"""
oyWingFeatherSetup by Erkan Ozgur Yilmaz (c) 2009

v10.5.17

Description:
------------
oyWingFeatherSetup is a script that helps you create a wing feather rig.

Version History:
----------------
v10.5.17
- modifications for Maya 2011 and Pymel 1.0.2

v10.5.13
- started to move to the new concept, where the wing and feathers are setup
  using a more robust method

v10.4.28
- removed the import oySetProject statement
- added parametric scale control for the tip curve creation
- added methods to adjust the rootSurfaceThickness
- fixed lower and upper profile curve generation methods
- updated doSetup method

v9.12.12
- added upper and lower profile curves and upper and lower profile surfaces
  which are derived from the profile curves by using a loft. This profile
  surfaces will help to intersect or pull the body feathers on to the wing
  feathers, which are mainly done in Houdini in our pipeline for now.

v9.11.30
- added showFeathers attribute to gradually control the visibilities of the
  feathers
- the range of the setDriven keyframes in the blenderObject is scaled 10 times
  for the deformer attributes, to let the user control the object more
  precisely

v9.11.26
- sets names are fixed to clearly see the groups
- now the script accepts only one object as the featherBaseObject, so one can
  use a simple plane for every feather
- the history is now deleted when a surface is created from a curve
- now only the tip scaleZ attribute of the tip curve is freezed
- the wing attributes are now checked for existancy before they are created in
  the blend controller

v9.11.21
- fixed an syntax error while creating the blend controller attributes

v9.11.18
- moved deformerVisibility attribute over switchOpenClose attribute

v9.11.16
- added deformer visibility attribute to the blend controller and connect it
  to the deformer handles
- created bend1 bend2 and twist attribute in the blend controller for every
  guide feather in a wingPart
- added axialCorrectionGroups for the openTipJoints and closedTipJoints
- added scale constraints between openTipJoints, closedTipJoints, and tipJoints
- created new sets for up and aim locators
- both the start and end angle attributes of the twist deformer are now
  connected to the blend controller object, and the limits changed to -180,180
- the lowBound and highbound attributes of the bend1 are changed to -10,10
- the upLocators now parented to the follicles back again

v9.11.15
- the need for an extra variable for the final joint in the wing and the tip
  is eliminated by using the last joint as another wing joint with 0 feathers
- while creating the tip joints the joints are now placed on the u=0.5 line
  with the corresponding v values from the wing follicle, to get a more even
  distribution
- created setDrivenKeyframes to animate the wing from open form to closed form
- created groups to group the created nodes
- created sets to group the created nodes

v9.11.14
- the Utility method now has new methods for animation curve editing
- there are now two other copy of the original wing which are going to be used
  in animation, as the open and closed wing controls

v9.11.13
- now the final tip joint of the wing is automatically added to calculation,
  there is no need to add it as another wing joint and try to add only one
  feather on it
- now the deformer attributes of the first and last feather in a wingPart
  object are blended over the internal feathers
- the feathers are now automatically oriented according to the direction of the
  wingPart, so the same feather object can be used to decorate both the right
  and left wings

v9.11.10
- still a development version
- created WingPart, Feather, Utility classes to organize the data more freely

v9.11.9
- still the development version
- placing the base feathers are now working in its most basic form

v9.11.8
- still the development version

v9.11.7
- the development version

-------------------------------------------------------------------------------

TODO:
-----
- Assign a shader for every feather layer

Done :
------
+ Create visibility switches for every thing
+ The newly created profile surface opens up another nice possibility to align
  the feathers to each other by using their twist attribute with the profile
  surface, think about it later
+ Create the neccesary connections to a set driven key system
+ Create joints by duplicating the original wing joint hierarchy, to easily
  blend between closed and open poses of the wing animations
+ Create a set for wing elements
+ Create a set for every wing part
+ Create a set for feather layers
+ The nodes that needs to be connected to a set driven key system are:
  * upLocators
  * aimLocators
  * the weights of the parent constrains of the
+ Create axialCorrection groups for up vectors

"""



import pymel.core as pm
import oyAuxiliaryFunctions as oyAux



__version__ = "10.5.17"



########################################################################
class Wing(object):
    """
    A tool to setup bird wings.
    
    ==========
    THE METHOD
    ==========
    
    ===============
    THE WING JOINTS
    ===============
    To correctly setup the wing, the wing joints needs to be in correct
    orientation. The joints should be placed in a near or exact T-Pose. The
    joints doesn't need to align with any axis, a relaxed pose should generaly
    work. You should have five joints in total for one wing (clavicle, humerus,
    ulna-radius, carpametacarpus, and the tip of the carpametacarpus).
    
    In both of the wings, the x-axis should point to the child joint. In the
    left joint the y-axis should point down, the z-axis should point to the
    rear of the bird (in orientation manner). Be careful about the last single,
    joint, it also needs to be in the same orientation with its parent.
    
    The wingJoints are duplicated twice to create the openWingJoints and
    closedWingJoints. The original wingJoints are controlled with these two
    joint chains. One of the joint set is designed to have a consistent closed
    shape of the wing. Rather making it folded by hand all the time, to make
    the transition a little bit smoother, it is setDriven to the closed
    orientation.
    
    =====================
    THE WING ROOT SURFACE
    =====================
    This method starts by creating a curve that crosses all the wing joints.
    This curve is called the wingRootCurve. wingRootCurve then used to create
    the wingRootSurface.
    
    wingRootSurface is the nurbs surface that all the follicles
    (wingRootFollicles) are going to be placed. It is smoothSkinned to the
    wingJoints to let it follow the wing mesh.
    
    ==================
    THE PROFILE CURVES
    ==================
    For every wing joint (generaly there are 6 joints and 4 feathers for each
    joint all togather 24x3=72 feathers in 24 groups of feathers) a curve is
    created ( with 4 CVs and 3rd degree) along the z-axis of the joint. These
    curves are called wingProfileCurves.
    
    To allow easy control of these wingProfileCurves, they are deformed with a
    bend deformer. This deformer is enough to have the bent shape of the
    feathers in most of the situations.
    
    Then these wingProfileCurves are turned in to hairs to generate the
    dynamicWingProfileCurves.
    
    The wingProfileCurves and dynamicWingProfileCurves are blended to
    blendedWingProfileCurves let the user choose between hand controlled or
    fully automated wing animation.
    
    ================
    THE WING SURFACE
    ================
    The blendedWingProfileCurves are connected with a loft surface node to
    reate the nurbs represantation of the wing which is called the
    openWingSurface. The openWingSurface is duplicated twice to generate the
    closedWingSurface and the finalWingSurface which is a blend between the
    two. The closedWingSurface allows the user to control the closed shape of
    the wingSurface by editing the CVs of the nurbs surface to get a smooth and
    user controlled wingSurface.
    
    The finalWingSurface is also converted to mesh (finalWingSurfaceAsMesh) to
    be able to export it to Houdini along with the other object in the setup to
    create bodyFeathers (another big story).
    
    =====================================
    THE FEATHERS & FEATHER PROFILE CURVES
    =====================================
    The feathers are sheets of polygonal patches.
    
    They are deformed with featherProfileCurves which are generated from
    isoparms of the finalWingSurface.
    
    This feather curves are used to deform a duplicate of the featherBaseObject
    with a wire deformer. The wire deformer is placed very smartly to allow it
    to deform the object only localy, this means that the base curve is also
    attached to the featherFollicle, so it is moving with the featherObject
    allowing only local deformations and avoiding a double transformation to
    happen.
    
    The feather also has a bend and a twist deformer. The bend deformer
    slightly curves the feather to get nice speculars in renders.
    
    The twist deformer has a smart rotation setup, which allows the feather
    root and tip to be always aligned with the finalWingSurface.
    
    Aligning the start and end angle of the twist with the wingSurface:
    * calculate the angle between tipProjectedLocalX and the
    rootProjectedLocalX of the twistHandle and the local normals in root and
    tip of the feather
    * to calculate the angles:
        * place two pointOnSurfaceInfo nodes for the tip and the end of the
        feather
        * set the parameters of pOSI node to match the tip of the wingSurface,
        generaly v=0-1 and u=n where and is the parametric value betweent 0...n
        * because the tangentU, tangentV and normal values of a nurbs surface
        can be some times non-orthogonal, just use tangentU and tangentV and
        their cross product in calculations as the normal
        * use the angleBetween node to get the angle and set it to the twist
        node
        * because the angleBetween node always generates positive values, it
        needs to be corrected by comparing the projectedLocalY vector with the
        axis vector from the anglebetween node
    
    ===========
    CONTROLLERS
    ===========
    The wing has fairly low controls. There are only the hand animated fk wing
    joints (openWingJoints) and the blender. The blender object allows the user
    to:
        * animate from open to closed or the reverse forms
        * control the bending of the feathers
        * control the dynamics of the wing ( damp, stiffness etc. )
        * control the noise of the feather tips
    
    =====
    SETUP
    =====
    The user of this script should give these values:
    * wing joints
    * wing name
    * wing controller
    * base feather
    * 
    """
    
    
    
    #----------------------------------------------------------------------
    def __init__(self):
        
        #self._feathers = []
        #self._lowerCovertFeathers = []
        #self._upperCovertFeathers = []
        #self._lowerGreaterCovertFeathers = []
        #self._upperGreaterCovertFeathers = []
        
        #self._featherCounts = 0
        #self._featherBaseObject = None
        
        #self._upperWingJoints = [] # composed from humerus and ulna-radius
        #self._lowerWingJoints = [] # composed from carpometacarpus and the digits
        
        #self._upperWingPart = None
        #self._lowerWingPart = None
        
        #self._wingRootFollicles = []
        #self._wingRootFolliclesData = []
        
        # the transform nodes that everything is going to be parented under
        #self._wingProfileConstraintGroups = []
        #self._wingProfileAimLocators = []
        #self._wingProfileUpLocators = []
        
        #self._wingParts = []
        
        self._wingJoints = []
        
        # *****************
        # real bone names in the wing
        # humerus
        # radius-ulna
        # carpometacarpus
        # major digit
        # minor digit
        # alula ( the fore finger )
        
        self._closedWingJoints = []
        self._openWingJoints = []
        
        # the parent constraints between the open, close and wing joints
        self._wingParentConstraints = []
        
        self._blender = None
        
        self._wingJointsCount = 0
        self._wingJointsLenghts = []
        
        self._direction = 1
        self._wingLength = 10.0
        self._wingRootSurfaceThickness = 1.0
        
        self._wingRootCurve = None
        self._wingRootProfileCurve1 = None
        self._wingRootProfileCurve2 = None
        self._wingRootSurface = None
        self._wingRootSurfaceSkinCluster = None
        
        self._wingProfileCurves = []
        self._wingDynamicProfileCurves = []
        self._wingDynamicProfileCurvesGroup = None
        self._wingBlendedProfileCurves = []
        self._wingBlendedProfileCurvesGroup = None
        self._wingProfileCurveBlendShapes = []
        
        self._wingOpenSurface = None
        self._wingClosedSurface = None
        self._wingSurface = None
        
        self._hairSystem = None
        
        self._dropOff = 1
        
        self._blenderAttributeName = 'switchOpenClose'
        
        self._wingName = ''
        self._masterGroup = None
        self._wingRootFolliclesGroup = None
        
        self._wingJointsSet = None
        self._openWingJointsSet = None
        self._closedWingJointsSet = None
        self._wingRootCurvesAndSurfacesSet = None
        self._wingRootFolliclesSet = None
        self._feathersSet = None
        self._upperCovertFeathersSet = None
        self._lowerCovertFeathersSet = None
        self._upperGreaterCovertFeathersSet = None
        self._lowerGreaterCovertFeathersSet = None
        self._bend1sSet = None
        self._twistsSet = None
        
        #self._wingJointExtends = []
        self._wingProfileJoints = []
    
    
    
    #----------------------------------------------------------------------
    def doSetup(self):
        """sets up all the feather rig
        """
        
        self.createOpenAndClosedWingJoints()
        self.createJointBlenders()
        self.createProfileJoints()
        
        self.createWingRootCurve()
        
        #self.createWingRootSurface()
        #self.rigWingRootSurface()
        #self.createWingRootFollicles()
        #self.createHairSystem()
        
        #self.createWingParts()
        
        #self.createWingProfileConstratintGroups()
        #self.createWingProfileAimTargets()
        #self.createWingProfileCurves()
        #self.createWingDynamicProfileCurves()
        #self.createWingBlendedProfileCurves()
        #self.createWingOpenSurface()
        
        #self.createWingClosedSurface()
        #self.createWingSurface()
        
        #self.createFeathers()
        #self.createFeatherBlenders()
        #self.createBlenderAttributes( self._blender )
        #self.connectBlenderController()
        #self.createProfileCurvesAndSurfaces()
        #self.createMasterGroups()
    
    
    
    #----------------------------------------------------------------------
    def wingLength():
        """a clever way of creating properties
        """
        def fget(self):
            return self._wingLength
        
        def fset(self, wingLength):
            self._wingLength = wingLength
        
        return locals()
    
    wingLength = property( **wingLength() )
    
    
    
    #----------------------------------------------------------------------
    def wingName():
        """another clever property
        """
        
        def fget(self):
            return self._wingName
        
        def fset(self, wingName):
            self._wingName = wingName
        
        return locals()
    
    wingName = property( **wingName() )
    
    
    
    #----------------------------------------------------------------------
    def wingJoints():
        
        def fget(self):
            return self._wingJoints
        
        def fset(self, joints):
            """sets the wing joints
            """
            
            assert(isinstance(self,Wing))
            
            #self._wingJoints = joints
            ##self._upperWingJoints = joints[:3]
            ##self._lowerWingJoints = joints[2:]
            
            ### create the partial wing data object and append the joints
            ##self._wingParts = []
            ##for joint in joints:
                ##tempWingP = WingPart()
                ##tempWingP.setRootJoint( joint )
                
                ##self._wingParts.append( tempWingP )
            
            
            #assert(isinstance(self, Wing))
            
            # create the WingJoint objects
            for joint in joints:
                wingJoint = WingJoint(joint)
                
                wingJoint.finLength = self._wingRootSurfaceThickness
                
                # for every joint create the fins
                wingJoint.createFins()
                
                self._wingJoints.append( wingJoint )
        
            self._wingJointsCount = len(self._wingJoints)
            # use the first joints direction
            #self._direction = self._wingParts[0].getDirection()
        
        return locals()
    
    wingJoints = property( **wingJoints() )
    
    
    
    #----------------------------------------------------------------------
    def wingRootJoint():
        
        def fget(self):
            return self._wingJoints[0]
        
        def fset(self, joint):
            """sets the wing root joint and then gets the other joints automatically
            """
            
            # find the 5 child joints and set them as the wing joints
            self.wingJoints = Utility.getJointList(joint, 5)
        
        return locals()
    
    wingRootJoint = property( **wingRootJoint() )
    
    
    
    #----------------------------------------------------------------------
    def featherCounts():
        
        def fget(self):
            return self._featherCounts
            
        def fset(self, featherCounts):
            """sets the feather counts for the joints
            """
            self._featherCounts = featherCounts
            
            ## update the wingPart objects
            #for i,count in enumerate(featherCounts):
                #self._wingParts[i].setFeatherCount( count )
        
        return locals()
    
    featherCounts = property( **featherCounts() )
    
    
    
    #----------------------------------------------------------------------
    def featherBaseobject():
        
        def fget(self):
            return self._featherBaseObject
        
        def fset(self, featherBaseObject):
            """sets the feather base object that is going to be copied over the
            feather joints
            """
            
            self._featherBaseObject = featherBaseObject
            
            #for i,wingP in enumerate(self._wingParts):
                
                #currentFeatherBaseObject = featherBaseObject
                #wingP.setFeatherBaseObject( currentFeatherBaseObject )
        
        return locals()
    
    featherBaseobject = property( **featherBaseobject() )
    
    
    
    #----------------------------------------------------------------------
    def createWingRootFollicles(self):
        """creates the wingRootFollicles
        """
        
        for joint in self._wingJoints:
            # get the closest point to the wingRootSurface
            # and create a follicle there
            data = Utility.closestPointOnSurface( joint, self._wingRootSurface )
            
            self._wingRootFolliclesData.append( data )
            
            newFollicle = Utility.createFollicle( self._wingRootSurface, data[1], data[2] )
            
            self._wingRootFollicles.append( newFollicle )
    
    
    
    ##----------------------------------------------------------------------
    #def createWingProfileConstratintGroups(self):
        #"""creates the profileCurves constraint group which is the transform node that
        #everything is parented to
        #"""
        
        ## do it for upper wing and then for lower part
        #for joints in self._upperWingJoints:
            #pass
    
    
    
    ##----------------------------------------------------------------------
    #def createWingProfileAimTargets(self):
        #"""creates the controllers and let the transform parents to aim that
        #controller
        #"""
        
        ## for every transform parent create a locator
        #for constraintGroup in self._wingProfileConstraintGroups:
            #aimLocator = pm.spaceLocator()
            #upLocator = pm.spaceLocator()
            
            ## parent it under the parent of the current constraintGroup
            #pm.parent( aimLocator, constraintGroup.getParent(), r=True )
            #pm.parent( upLocator, constraintGroup.getParent(), r=True )
            
            ## move it to the tip of the wing
            #aimLocator.setAttr('tz', self._wingLength * self._direction)
            #upLocator.setAttr('ty', -self._direction )
            
            ## create a projection of it
            #projectedLocator = Utility.createProjection( aimLocator, (0,self._direction,0), constraintGroup.getParent() )
            
            ## aim constraintGroup to the projectedLocator
            #pm.aimConstraint( projectedLocator,
                              #constraintGroup,
                              #mo=False,
                              #w=1,
                              #aimVector=(0,0,1),
                              #upVector=(0,1,0),
                              #worldUpObject=upLocator,
                              #worldUpType='object'
                              #)
            
            ## limit the locator in XZ-plane
            ##assert(isinstance(locator, pm.Transform))
            ##locator.attr('ty').lock()
            
            ## store it
            #self._wingProfileAimLocators.append( aimLocator )
            #self._wingProfileUpLocators.append( upLocator )
        
        ## point constraint all the locators between eachother
        #Utility.groupConstraint( self._wingProfileAimLocators )
    
    
    
    ##----------------------------------------------------------------------
    #def createWingOpenSurface(self):
        #"""creates the open wing surface
        #"""
        
        ## create a surface by lofting the wingProfileCurves
        #self._wingOpenSurface = Utility.createSurfaceFromCurves( self._wingBlendedProfileCurves, True )
        #self._wingOpenSurface.rename( self._wingName + '_wingOpenSurface#')
    
    
    
    ##----------------------------------------------------------------------
    #def createWingProfileCurves(self):
        #"""creates the wing profile curves which are owned by the wing itself
        #"""
        ## create a profile curve in the origin then parent a duplicate to the
        ## follicle
        
        #CVPositions = [(0,0,0),
                       #(0,0,0.3333 * self._wingLength),
                       #(0,0,0.6667 * self._wingLength),
                       #(0,0,1.0 * self._wingLength)
                       #]
        
        #baseProfileCurve = Utility.createCurve( CVPositions )
        
        #for constraintGroup in self._wingProfileConstraintGroups:
            ## duplicate the curve
            #dupCurve = pm.duplicate( baseProfileCurve )[0]
            
            ## parent it under the transform parent
            #pm.parent( dupCurve, constraintGroup )
            
            ## zero transforms
            #oyAux.goHome( dupCurve )
            
            ## rename it
            #dupCurve = pm.rename( dupCurve, self._wingName + '_wingProfileCurve#' )
            
            ## store it
            #self._wingProfileCurves.append( dupCurve )
        
        ## delete the baseProfileCurve
        #pm.delete( baseProfileCurve )
    
    
    
    #----------------------------------------------------------------------
    def createWingCloseSurface(self):
        """creates the closed wing surface
        """
        
        # just create a duplicate of the closed wing surface and rig it
        # to the 
        
    
    
    
    ##----------------------------------------------------------------------
    #def createWingDynamicProfileCurves(self):
        #"""these are the duplicate of the wingProfileCurves which are then
        #converted to hairs
        #"""
        
        ## also connect them to the wingRootFollicles
        ## and make the follicles dynamic
        
        ## create the wingDynamicProfileCurvesGroup
        #pm.select(None)
        #self._wingDynamicProfileCurvesGroup = pm.group(em=True)
        #self._wingDynamicProfileCurvesGroup.rename( self._wingName + '_wingDynamicProfileCurvesGroup' )
        
        ## create the hairSystem
        #self._wingHairSystem = Utility.createHairSystem()
        
        #for curve,follicle in zip(self._wingProfileCurves, self._wingRootFollicles):
            ## convert the wingProfileCurves to dynamic curves
            #dynamicCurve = Utility.convertToHair( curve, follicle, self._wingHairSystem, False )
            
            ## parent it
            #pm.parent( dynamicCurve, self._wingDynamicProfileCurvesGroup )
            
            ## rename it
            #dynamicCurve.rename( self._wingName + '_wingDynamicCurve#' )
            
            ## add it to the list
            #self._wingDynamicProfileCurves.append( dynamicCurve )
    
    
    
    #----------------------------------------------------------------------
    def createFeatherBlenders(self):
        """invokes wingPart objects to create blenders between the first and
        the last feather of the wingPart
        """
        for wingP in self._wingParts:
            #assert(isinstance(wingP, WingPart))
            wingP.createFeatherBlenders()
    
    
    
    #----------------------------------------------------------------------
    def createFeathers(self):
        """invokes wingPart objects createFeathers to create feather objects
        """
        
        self._feathers = []
        self._upperCovertFeathers = []
        self._lowerCovertFeathers = []
        self._upperGreaterCovertFeathers = []
        self._lowerGreaterCovertFeathers = []
        
        for wingP in self._wingParts:
            assert(isinstance(wingP,WingPart))
            wingP.createFeathers()
            
            # store the feathers
            for feather in wingP.getFeathers():
                self._feathers.append( feather )
            
            for lowerCovertFeather in wingP.lowerCovertFeathers:
                self._lowerCovertFeathers.append( lowerCovertFeather )
            
            for upperCovertFeather in wingP.upperCovertFeathers:
                self._upperCovertFeathers.append( upperCovertFeather )
            
            for lowerGreaterCovertFeather in wingP.lowerGreaterCovertFeathers:
                self._lowerGreaterCovertFeathers.append( lowerGreaterCovertFeather )
            
            for upperGreaterCovertFeather in wingP.upperGreaterCovertFeathers:
                self._upperGreaterCovertFeathers.append( upperGreaterCovertFeather )
    
    
    
    #----------------------------------------------------------------------
    def createProfileCurvesAndSurfaces(self):
        """invokes the wingPart objects createProfileCurves to create the
        profile curves
        """
        
        self._upperProfileCurves = []
        self._lowerProfileCurves = []
        
        for wingP in self._wingParts:
            assert(isinstance(wingP,WingPart))
            wingP.createProfileCurves()
            
            for upperProfileCurve in wingP.upperProfileCurves:
                self._upperProfileCurves.append( upperProfileCurve )
            
            for lowerProfileCurve in wingP.lowerProfileCurves:
                self._lowerProfileCurves.append( lowerProfileCurve )
        
        # create the profile surfaces
        self._upperProfileSurface = pm.loft( self._upperProfileCurves, ar=False, n='upperProfileSurface#' )
        self._lowerProfileSurface = pm.loft( self._lowerProfileCurves, ar=False, n='lowerProfileSurface#' )
    
    
    
    #----------------------------------------------------------------------
    def createProfileJoints(self):
        """creates the profile joints
        """
        
        # don't create a profile for the first joint (clavicle)
        for i in range(1, self._wingJointsCount):
            
            profileJoint = ProfileJoint()
            profileJoint.length = self._wingLength
            
            # parent it to the corresponding joint
            pm.parent( profileJoint.ACGroup, self._wingJoints[i].joint, r=1 )
            
            # create parent constraint between the extendedJoints and
            # the profile joints
            if i != 0 and i < self._wingJointsCount-1:
                profileJoint.controllers = (
                    self._wingJoints[i].rootFin,
                    self._wingJoints[i-1].tipFin
                )
                
                profileJoint.connectToControllers()
            
            self._wingProfileJoints.append( profileJoint )
    
    
    
    #----------------------------------------------------------------------
    def createWingParts(self):
        """creates the two wing part objects, one for upper, one for lower wing
        """
        
        # the upper part
        self._upperWingPart = WingPartNew()
        self._upperWingPart.joints = self._wingJoints#self._upperWingJoints
        self._upperWingPart.direction = self._direction
        self._upperWingPart.length = self._wingLength
        self._upperWingPart.hairSystem = self._hairSystem
        self._upperWingPart.name = self._wingName + '_upper'
        self._upperWingPart.rootFollicles = self._wingRootFollicles #[:3]
        self._upperWingPart.rootCurve = self._wingRootCurve
        self._upperWingPart.doSetup()
        
        ## the lower part
        #self._lowerWingPart = WingPartNew()
        #self._lowerWingPart.joints = self._lowerWingJoints
        #self._lowerWingPart.direction = self._direction
        #self._lowerWingPart.length = self._wingLength
        #self._lowerWingPart.hairSystem = self._hairSystem
        #self._lowerWingPart.name = self._wingName + '_lower'
        #self._lowerWingPart.rootFollicles = self._wingRootFollicles[2:]
        #self._lowerWingPart.doSetup()
    
    
    
    ##----------------------------------------------------------------------
    #def createWingRootCurve(self):
        #"""creates the wing root curve
        #"""
        
        ## to make it easy use locators to get the world positions and delete
        ## the locators when ever the job is done
        
        ## create a root locator
        #locator = pm.spaceLocator()
        
        #cvList = []
        
        ## add the first joints position by hand
        #cvList.append( pm.xform( self._wingJoints[0], q=True, ws=True, t=True ) )
        
        ## duplicate the locator over the joints
        #for i in range( self._wingJointsCount - 1 ):
            ##joint = self._wingParts[i].getRootJoint()
            #joint = self._wingJoints[i]
            #divisionCount = 3
            
            #newLocators = []
            ##newLocators = Utility.duplicateOverJoint( joint, locator, divisionCount )
            #newLocators = Utility.duplicateOverBone( joint, locator, divisionCount, False )
            
            #for aLocator in newLocators:
                #cvList.append( pm.xform(aLocator,q=True,ws=True,t=True) )
            
            #pm.delete( newLocators )
        
        ## delete the temp locator
        #pm.delete( locator )
        
        ## create the curve
        #self._wingRootCurve = Utility.createCurve( cvList )
        #self._wingRootCurve.rename( self.wingName + '_rootCurve#' )
        
        ## create a temp locator point constraint it to the first joint
        ## orientConstraint it with all the wing joints
        ## align the curve axis with that locator
        #tempLocator = pm.spaceLocator()
        #pm.pointConstraint( self._wingJoints[0], tempLocator )
        #for joint in self._wingJoints:
            #pm.orientConstraint( joint, tempLocator, w=1.0 )
        
        ## parent it to that locator and freeze it
        #pm.parent( self._wingRootCurve, tempLocator)
        
        #pm.makeIdentity( self._wingRootCurve, apply=True, t=1, r=1, s=1 )
        #pm.xform(self._wingRootCurve, os=True, piv=(0.0,0.0,0.0))
        #pm.parent( self._wingRootCurve, w=True, a=True )
        
        ## delete the tempLocator
        #pm.delete( tempLocator )
        
        ## set the wingParts root curves
        #for wingP in self._wingParts:
            #wingP.setWingRootCurve( self._wingRootCurve )
    
    
    
    #----------------------------------------------------------------------
    def createWingRootCurve(self):
        """creates the wing root curve
        """
        
        cvList = []
        
        for profileJoint in self._wingProfileJoints:
            assert(isinstance(profileJoint, ProfileJoint))
            
            cvList.append( pm.xform(profileJoint.rootJoint, q=1, ws=1, t=1) )
        
        self._wingRootCurve = Utility.createCurve( cvList, True )
        self._wingRootCurve.rename( self.wingName + '_rootCurve#' )
        
        # create a temp locator point constraint it to the first joint
        # orientConstraint it with all the wing joints
        # align the curve axis with that locator
        tempLocator = pm.spaceLocator()
        pm.pointConstraint( self._wingJoints[1].joint, tempLocator )
        
        for i in range(1,len(self._wingJoints)):
            pm.orientConstraint( self._wingJoints[i].joint, tempLocator, w=1.0 )
        
        # parent it to that locator and freeze it
        pm.parent( self._wingRootCurve, tempLocator)
        
        pm.makeIdentity( self._wingRootCurve, apply=True, t=1, r=1, s=1 )
        pm.xform(self._wingRootCurve, os=True, piv=(0.0,0.0,0.0))
        pm.parent( self._wingRootCurve, w=True, a=True )
        
        # delete the tempLocator
        pm.delete( tempLocator )
        
        ## set the wingParts root curves
        #for wingP in self._wingParts:
            #wingP.setWingRootCurve( self._wingRootCurve )
    
    
    
    #----------------------------------------------------------------------
    def wingRootSurfaceThickness():
        
        def fget(self):
            return self._wingRootSurfaceThickness
        
        def fset(self, thickness):
            self._wingRootSurfaceThickness = thickness
            
            #assert(isinstance(self,Wing))
            for wingJoint in self._wingJoints:
                #assert(isinstance(wingJoint, WingJoint))
                wingJoint.finLength = thickness
                
        return locals()
    
    wingRootSurfaceThickness = property( **wingRootSurfaceThickness() )
    
    
    
    #----------------------------------------------------------------------
    def createWingRootSurface(self):
        """creates the wingRootSurface
        """
        
        self._wingRootSurface, \
        self._wingRootProfileCurve1, \
        self._wingRootProfileCurve2 = Utility.createSurfaceFromCurve(
            self._wingRootCurve,
            (0, self._direction, 0),
            self._wingRootSurfaceThickness )
        
        self._wingRootSurface.rename( self.wingName + "_wingRootSurface#" )
        self._wingRootProfileCurve1.rename( self.wingName + "_wingRootProfileCurveA#" )
        self._wingRootProfileCurve2.rename( self.wingName + "_wingRootProfileCurveB#" )
        
        ## set the wingParts wing root surface
        #for wingP in self._wingParts:
            ##assert(isinstance(wingP, WingPart ))
            #wingP.setWingRootSurface( self._wingRootSurface )
    
    
    
    #----------------------------------------------------------------------
    def rigWingRootSurface(self):
        """rigs the wingRootSurface to the wingJoints
        """
        
        rootJoints = []
        
        pm.select( self._wingRootSurface, self._wingJoints )
        self._wingRootSurfaceSkinCluster = pm.skinCluster( tsb=True, dr=self._dropOff )[0]
        
        pm.select( None )
        
        self._wingRootSurfaceSkinCluster.rename( self.wingName + "_wingRootSurface_skinCluster#" )
    
    
    
    #----------------------------------------------------------------------
    def rootFollicles():
        
        def fget(self):
            return self._wingRootFollicles
        
        def fset(self, follicles):
            self._wingRootFollicles = follicles
        
        return locals()
    
    rootFollicles = property( **rootFollicles() )
    
    
    
    #----------------------------------------------------------------------
    def createOpenAndClosedWingJoints(self):
        """creates the joint chains to blend the open and closed wing
        animations
        """
        
        baseRadius = self._wingJoints[0].joint.getAttr('radius')
        
        closedWingStartJoint = Utility.duplicateJointChain( self._wingJoints[0].joint )
        self._closedWingJoints = Utility.getJointList( closedWingStartJoint, self._wingJointsCount )
        
        # set their radius to two times of the previous one
        for joint in self._closedWingJoints:
            joint.setAttr( 'radius', baseRadius * 2 )
            joint.rename ( self.wingName + "_closedWingJoint#" )
        
        openWingStartJoint = Utility.duplicateJointChain( self._wingJoints[0].joint )
        self._openWingJoints = Utility.getJointList( openWingStartJoint, self._wingJointsCount )
        
        # set their radius to three times of the previous one
        for joint in self._openWingJoints:
            joint.setAttr( 'radius', baseRadius * 3 )
            joint.rename ( self.wingName + "_openWingJoint#" )
        
        #for i, wingP in enumerate(self._wingParts):
            ##assert(isinstance(wingP, WingPart))
            #wingP.closedWingJoint = self._closedWingJoints[i]
            #wingP.openWingJoint = self._openWingJoints[i]
    
    
    
    #----------------------------------------------------------------------
    def createHairSystem(self):
        """creates the hair system node
        """
        # create the hairSystem
        self._hairSystem = Utility.createHairSystem()
    
    
    
    #----------------------------------------------------------------------
    def createJointBlenders(self):
        """creates parent constraints between open, close and rigged joints
        """
        
        # create the parent constraints
        for i in range(self._wingJointsCount):
            # wing joints
            pCnst = pm.parentConstraint( self._openWingJoints[i], self._wingJoints[i], w=1.0 )
            pCnst = pm.parentConstraint( self._closedWingJoints[i], self._wingJoints[i], w=0.0 )
            pCnst.rename( self.wingName + "_wing_parentConstraint#" )
            
            # set interpolation to shortest
            pCnst.setAttr('interpType', 2)
            
            self._wingParentConstraints.append( pCnst )
    
    
    
    #----------------------------------------------------------------------
    def blendController():
        
        def fget(self):
            return self._blender
        
        def fset(self, blenderObject):
            """sets the blender object, that blends open and closed wing
            """
            
            # prefix the blender name
            blenderObject.rename ( self.wingName + "_blender#" )
            
            self._blender = blenderObject
        
        return locals()
    
    blendController = property( **blendController() )
    
    
    
    #----------------------------------------------------------------------
    def connectBlenderController(self):
        """connects the blender controller to the objects by using set driven
        keys
        """
        
        # the range is 0-10
        
        blenderAttrAsStr = self._blender.name() + "." + self._blenderAttributeName
        
        # wings parent constraint
        for i in range(len(self._wingParentConstraints)):
            
            # get the weight alias list
            wal = pm.parentConstraint( self._wingParentConstraints[i], q=True, wal=True)
            
            # open attr
            attr = wal[0]
            pm.setDrivenKeyframe( self._wingParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=1.0 )
            pm.setDrivenKeyframe( self._wingParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=0.0 )
            
            # close attr
            attr = wal[1]
            pm.setDrivenKeyframe( self._wingParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=0.0 )
            pm.setDrivenKeyframe( self._wingParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=1.0 )
        
        
        
        # tips parent and scale constraints
        for i in range(len(self._tipParentConstraints)):
            
            # parent constraints
            # get the weight alias list
            wal = pm.parentConstraint( self._tipParentConstraints[i], q=True, wal=True )
            
            # open attr
            attr = wal[0]
            pm.setDrivenKeyframe( self._tipParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=1.0 )
            pm.setDrivenKeyframe( self._tipParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=0.0 )
            
            # close attr
            attr = wal[1]
            pm.setDrivenKeyframe( self._tipParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=0.0 )
            pm.setDrivenKeyframe( self._tipParentConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=1.0 )
            
            
            # scale constraint
            wal = pm.scaleConstraint( self._tipScaleConstraints[i], q=True, wal=True )
            
            #open attr
            attr = wal[0]
            pm.setDrivenKeyframe( self._tipScaleConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=1.0 )
            pm.setDrivenKeyframe( self._tipScaleConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=0.0 )
            
            attr = wal[1]
            pm.setDrivenKeyframe( self._tipScaleConstraints[i], at=attr, cd=blenderAttrAsStr, dv=0.0, v=0.0 )
            pm.setDrivenKeyframe( self._tipScaleConstraints[i], at=attr, cd=blenderAttrAsStr, dv=10.0, v=1.0 )
        
        
        
        # upLocators positions and deformers
        for i,wingP in enumerate(self._wingParts):
            assert(isinstance(wingP, WingPart))
            
            currentFeathers = wingP.getFeathers()
            for feather in currentFeathers:
                #assert(isinstance(feather,Feather))
                
                # just create the open pose values the closed one will be prepared by the user
                pm.setDrivenKeyframe( feather.upLocator, at='tx', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                pm.setDrivenKeyframe( feather.upLocator, at='ty', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                pm.setDrivenKeyframe( feather.upLocator, at='tz', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                
                pm.setDrivenKeyframe( feather.upLocator, at='tx', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                pm.setDrivenKeyframe( feather.upLocator, at='ty', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                pm.setDrivenKeyframe( feather.upLocator, at='tz', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                
                pm.setDrivenKeyframe( feather.aimLocator, at='tx', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                pm.setDrivenKeyframe( feather.aimLocator, at='ty', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                pm.setDrivenKeyframe( feather.aimLocator, at='tz', cd=blenderAttrAsStr, dv=0.0, v=0.0)
                
                pm.setDrivenKeyframe( feather.aimLocator, at='tx', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                pm.setDrivenKeyframe( feather.aimLocator, at='ty', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                pm.setDrivenKeyframe( feather.aimLocator, at='tz', cd=blenderAttrAsStr, dv=10.0, v=0.0)
                
                # visibilities of deformer handles
                assert( isinstance(feather,Feather))
                bend1 = feather.bend1[1]
                bend2 = feather.bend2[1]
                lattice = feather.latticeData.lattice
                self._blender.attr('deformerVisibility') >> bend1.attr('v')
                self._blender.attr('deformerVisibility') >> bend2.attr('v')
                self._blender.attr('deformerVisibility') >> lattice.attr('v')
                
                
            # connect blend attributes to the first and last feather
            if len( currentFeathers ) > 0:
                # bend1
                attrName = 'w' + str(i) + '_Bend1Start'
                bend1Start = currentFeathers[0].getBend1()
                bend1StartDeformer = bend1Start[0]
                bend1StartHandle = bend1Start[1]
                pm.setDrivenKeyframe( bend1StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=-10.0, v=-1.0 )
                pm.setDrivenKeyframe( bend1StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                pm.setDrivenKeyframe( bend1StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=10.0, v=1.0 )
                
                attrName = 'w' + str(i) + '_Bend1End'
                bend1End = currentFeathers[-1].getBend1()
                bend1EndDeformer = bend1End[0]
                bend1EndHandle = bend1End[1]
                pm.setDrivenKeyframe( bend1EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=-10.0, v=-1.0 )
                pm.setDrivenKeyframe( bend1EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                pm.setDrivenKeyframe( bend1EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=10.0, v=1.0 )
                
                # bend2
                attrName = 'w' + str(i) + '_Bend2Start'
                bend2Start = currentFeathers[0].getBend2()
                bend2StartDeformer = bend2Start[0]
                bend2StartHandle = bend2Start[1]
                pm.setDrivenKeyframe( bend2StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=-10.0, v=-1.0 )
                pm.setDrivenKeyframe( bend2StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                pm.setDrivenKeyframe( bend2StartDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=10.0, v=1.0 )
                
                attrName = 'w' + str(i) + '_Bend2End'
                bend2End = currentFeathers[-1].getBend2()
                bend2EndDeformer = bend2End[0]
                bend2EndHandle = bend2End[1]
                pm.setDrivenKeyframe( bend2EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=-10.0, v=-1.0 )
                pm.setDrivenKeyframe( bend2EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                pm.setDrivenKeyframe( bend2EndDeformer, at='curvature', cd=self._blender.attr( attrName ), dv=10.0, v=1.0 )
                
                ## twist
                #attrName = 'w' + str(i) + '_TwistStartRootAngle'
                #twistStart = currentFeathers[0].getTwist()
                #twistStartDeformer = twistStart[0]
                #twistStartHandle = twistStart[1]
                ## start/root angle
                #pm.setDrivenKeyframe( twistStartDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=-10.0, v=-180.0 )
                #pm.setDrivenKeyframe( twistStartDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                #pm.setDrivenKeyframe( twistStartDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=10.0, v=180.0 )
                ## end/tip angle
                #attrName = 'w' + str(i) + '_TwistStartTipAngle'
                #pm.setDrivenKeyframe( twistStartDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=-10.0, v=-180.0 )
                #pm.setDrivenKeyframe( twistStartDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                #pm.setDrivenKeyframe( twistStartDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=10.0, v=180.0 )
                
                #attrName = 'w' + str(i) + '_TwistEndRootAngle'
                #twistEnd = currentFeathers[-1].getTwist()
                #twistEndDeformer = twistEnd[0]
                #twistEndHandle = twistEnd[1]
                ## start/root angle
                #pm.setDrivenKeyframe( twistEndDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=-10.0, v=-180.0 )
                #pm.setDrivenKeyframe( twistEndDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                #pm.setDrivenKeyframe( twistEndDeformer, at='startAngle', cd=self._blender.attr( attrName ), dv=10.0, v=180.0 )
                ## end/tip angle
                #attrName = 'w' + str(i) + '_TwistEndTipAngle'
                #pm.setDrivenKeyframe( twistEndDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=-10.0, v=-180.0 )
                #pm.setDrivenKeyframe( twistEndDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=0.0, v=0.0 )
                #pm.setDrivenKeyframe( twistEndDeformer, at='endAngle', cd=self._blender.attr( attrName ), dv=10.0, v=180.0 )
                
                
                # -- FEATHER VISIBILITES --
                # connect the showFeathers to the feathers visibility attr
                
                # get all the base feathers
                for feather in wingP.feathers:
                    
                    #assert(isinstance(feather, Feather))
                    
                    pm.setDrivenKeyframe( feather.getFeatherObject() , at='v', cd=self._blender.attr( 'showFeathers' ), dv=0, v=0 )
                    pm.setDrivenKeyframe( feather.getFeatherObject() , at='v', cd=self._blender.attr( 'showFeathers' ), dv=1, v=1 )
                
                # the lower covert feathers
                for cFeather in wingP.lowerCovertFeathers:
                    pm.setDrivenKeyframe( cFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=1, v=0 )
                    pm.setDrivenKeyframe( cFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=2, v=1 )
                
                # the upper covert feathers
                for cFeather in wingP.upperCovertFeathers:
                    pm.setDrivenKeyframe( cFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=1, v=0 )
                    pm.setDrivenKeyframe( cFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=2, v=1 )
                
                # the lower greater covert feathers
                for gCFeather in wingP.lowerGreaterCovertFeathers:
                    pm.setDrivenKeyframe( gCFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=1, v=0 )
                    pm.setDrivenKeyframe( gCFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=2, v=1 )
                
                # the lower greater covert feathers
                for gCFeather in wingP.upperGreaterCovertFeathers:
                    pm.setDrivenKeyframe( gCFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=1, v=0 )
                    pm.setDrivenKeyframe( gCFeather.getFeatherObject(), at='v', cd=self._blender.attr( 'showFeathers' ), dv=2, v=1 )
    
    
    
    ##----------------------------------------------------------------------
    #def createWingBlendedProfileCurves(self):
        #"""creates the WingBlendedProfileCurves (obviously)
        
        #which are then used to create the wingSurface
        #"""
        
        ## create the blendedProfileCurves group
        #self._wingBlendedProfileCurvesGroup = pm.group( em=True, n=(self._wingName + '_wingBlendedProfileCurveGroup#'))
        
        #for profileCurve, dynaCurve in zip( self._wingProfileCurves, self._wingDynamicProfileCurves ):
            
            ## create a duplicate of the profileCurve
            #blendedCurve = pm.duplicate( profileCurve )[0]
            
            ## rename the curve
            #blendedCurve.rename( self._wingName + '_wingBlendedProfileCurve#' )
            
            ## store the curve
            #self._wingBlendedProfileCurves.append( blendedCurve )
            
            ## create a blendShape between profileCurve, dynaCurve and
            #pm.select( profileCurve, dynaCurve, blendedCurve )
            
            #blendShape = pm.blendShape( foc=True, o='world' )[0]
            
            ## set the weight of the profileCurve to 1
            #blendShape.setAttr( profileCurve.name(), 1 )
            
            ## store the blendShape
            #self._wingProfileCurveBlendShapes.append( blendShape )
            
            ## parent the curve under the wingBlendedProfileCurvesGroup
            #pm.parent( blendedCurve, self._wingBlendedProfileCurvesGroup )
            
            ## set them to zero
            #oyAux.goHome( blendedCurve )
    
    
    
    #----------------------------------------------------------------------
    def createBlenderAttributes(self, blenderObject):
        """creates the blender attributes in the blender object
        """
        
        
        #assert(isinstance(blenderObject, pm.nodetypes.Transform) )
        
        # deformer visibilities
        if not blenderObject.hasAttr('deformerVisibility'):
            blenderObject.addAttr('deformerVisibility', at='long', min=0, max=1, defaultValue=0 )
            blenderObject.setAttr('deformerVisibility', e=True, cb=True )
        
        # showFeathers
        if not blenderObject.hasAttr('showFeathers'):
            blenderObject.addAttr('showFeathers', at='long', min=0, max=2, defaultValue=0 )
            blenderObject.setAttr('showFeathers', e=True, cb=True )
        
        # switchOpenClose
        if not blenderObject.hasAttr(self._blenderAttributeName):
            blenderObject.addAttr(self._blenderAttributeName, at='float', k=True, min=0, max=10, defaultValue=0.0)
        
        # deformers
        for i,wingP in enumerate(self._wingParts):
            
            # if there are any feathers
            if len(wingP.getFeathers()) > 0:
                # create wingPi_bend1, wingPi_bend2, wingPi_twist for every wingPart objects
                # separator
                attrName = 'w' + str(i)
                if not blenderObject.hasAttr( attrName ):
                    blenderObject.addAttr( attrName, at='enum', enumName='---:0', k=True )
                    blenderObject.attr(attrName).lock()
                
                # bend1
                attrName = 'w' + str(i) + '_Bend1Start'
                if not blenderObject.hasAttr(attrName):
                    blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                attrName = 'w' + str(i) + '_Bend1End'
                if not blenderObject.hasAttr(attrName):
                    blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                # bend2
                attrName = 'w' + str(i) + '_Bend2Start'
                if not blenderObject.hasAttr(attrName):
                    blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                attrName = 'w' + str(i) + '_Bend2End'
                if not blenderObject.hasAttr(attrName):
                    blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                ## twist
                #attrName = 'w' + str(i) + '_TwistStartRootAngle'
                #if not blenderObject.hasAttr(attrName):
                    #blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                #attrName = 'w' + str(i) + '_TwistStartTipAngle'
                #if not blenderObject.hasAttr(attrName):
                    #blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                #attrName = 'w' + str(i) + '_TwistEndRootAngle'
                #if not blenderObject.hasAttr(attrName):
                    #blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
                
                #attrName = 'w' + str(i) + '_TwistEndTipAngle'
                #if not blenderObject.hasAttr(attrName):
                    #blenderObject.addAttr(attrName, at='float', k=True, defaultValue=0.0 )
    
    
    
    #----------------------------------------------------------------------
    def createClosedWingSurface(self):
        """creates the closed wing surface, which is a blend shaped target to
        """
        
        # duplicate the wingSurface without any history
        # 
        
    
    
    
    #----------------------------------------------------------------------
    def createMasterGroups(self):
        """creates a group that holds all the nodes except the parented tip
        joints
        """
        
        pm.select(None)
        self._masterGroup = pm.group(em=True, n=self._wingName + "_masterSetupGroup#")
        masterGroup = self._masterGroup
        
        # the curves and surfaces
        # root
        pm.parent( self._rootCurve, masterGroup )
        pm.parent( self._rootProfileCurve1, masterGroup )
        pm.parent( self._rootProfileCurve2, masterGroup )
        pm.parent( self._rootSurface, masterGroup )
        
        # tip
        pm.parent( self._tipCurve, masterGroup )
        pm.parent( self._tipProfileCurve1, masterGroup )
        pm.parent( self._tipProfileCurve2, masterGroup )
        pm.parent( self._tipSurface, masterGroup )
        
        # create rootFollicles group
        self._rootFolliclesGroup = pm.group ( em=True, n=self._wingName + "_rootFolliclesGroup#")
        pm.parent( self._rootFollicles, self._rootFolliclesGroup )
        
        # create tipFollicles group
        self._tipFolliclesGroup = pm.group ( em=True, n=self._wingName + "_tipFolliclesGroup#" )
        pm.parent( self._tipFollicles, self._tipFolliclesGroup )
        
        # parent to the main group
        pm.parent( self._rootFolliclesGroup, masterGroup )
        pm.parent( self._tipFolliclesGroup, masterGroup )
        
        # thats all about grouping
        # lets create sets
        
        # create a sets for:
        # the wingJoints
        # the open wingJoints
        # the closed wingJoints
        # the tipJoints
        # the open tipJoints
        # the closed tipJoints
        # root curves, profile curves, surfaces etc
        # tip curves, profile curves, surfaces etc.
        # root follicles
        # tip follicles
        # feathers
        # upper covert feathers
        # lower covert feathers
        # upper greater covert feathers
        # lower greater covert feathers
        # deformer handles: bend1, bend2, twist
        
        self._wingJointsSet = pm.sets( self._wingJoints, name=self._wingName + '_wing_baseJointsSet#' )
        self._openWingJointsSet = pm.sets( self._openWingJoints, name=self._wingName + '_wing_openJointsSet#' )
        self._closedWingJointsSet = pm.sets( self._closedWingJoints, name=self._wingName + '_wing_closedJointsSet#' )
        self._tipJointsSet = pm.sets( self._tipJoints, name=self._wingName + '_tip_baseJointsSet#' )
        self._openTipJointsSet = pm.sets( self._openTipJoints, name=self._wingName + '_tip_openJointsSet#' )
        self._closedTipJointsSet = pm.sets( self._closedTipJoints, name=self._wingName + '_tip_closedJointsSet#' )
        self._rootCurvesAndSurfacesSet = pm.sets( self._rootCurve, self._rootProfileCurve1, self._rootProfileCurve2, self._rootSurface, name=self._wingName + '_curvesAndSurfaces_rootSet#' )
        self._tipCurvesAndSurfacesSet = pm.sets( self._tipCurve, self._tipProfileCurve1, self._tipProfileCurve2, self._tipSurface , name=self._wingName + '_curvesAndSurfaces_tipSet#' )
        self._rootFolliclesSet = pm.sets( self._rootFollicles, name=self._wingName + '_follicles_rootSet#' )
        self._tipFolliclesSet = pm.sets( self._tipFollicles, name=self._wingName + '_follicles_tipSet#' )
        self._feathersSet = pm.sets( map( Feather.getFeatherObject, self._feathers ), name=self._wingName + '_feathers_baseSet#' )
        self._lowerCovertFeathersSet = pm.sets( map( Feather.getFeatherObject, self._lowerCovertFeathers ), name=self._wingName + '_feathers_lowerCovertSet#' )
        self._upperCovertFeathersSet = pm.sets( map( Feather.getFeatherObject, self._upperCovertFeathers ), name=self._wingName + '_feathers_upperCovertSet#' )
        self._lowerGreaterCovertFeathersSet = pm.sets( map( Feather.getFeatherObject, self._lowerGreaterCovertFeathers), name=self.wingName + '_feathers_lowerGreaterCovertSet#' )
        self._upperGreaterCovertFeathersSet = pm.sets( map( Feather.getFeatherObject, self._upperGreaterCovertFeathers), name=self.wingName + '_feathers_upperGreaterCovertSet#' )
        
        self._lowerProfileCurvesSet = pm.sets( self._lowerProfileCurves, name=self._wingName + '_curvesAndSurfaces_lowerProfileCurvesSet#' )
        self._upperProfileCurvesSet = pm.sets( self._upperProfileCurves, name=self._wingName + '_curvesAndSurfaces_upperProfileCurvesSet#' )
        
        self._lowerProfileSurfaceSet = pm.sets( self._lowerProfileSurface, name=self._wingName + '_curvesAndSurfaces_lowerProfileSurfaceSet#' )
        self._upperProfileSurfaceSet = pm.sets( self._upperProfileSurface, name=self._wingName + '_curvesAndSurfaces_upperProfileSurfaceSet#' )
        
        bend1s = []
        bend2s = []
        #twists = []
        lattices = []
        
        bend1Guides = []
        bend2Guides = []
        #twistGuides = []
        latticeGuides = []
        
        upLocators = []
        aimLocators = []
        
        for wingP in self._wingParts:
            assert(isinstance(wingP, WingPart))
            feathers = wingP.getFeathers()
            for feather in feathers:
                #assert(isinstance(feather, Feather))
                bend1s.append( feather.getBend1()[0] )
                bend2s.append( feather.getBend2()[0] )
                #twists.append( feather.getTwist()[0] )
                lattices.append( feather.latticeData.lattice )
                
                upLocators.append( feather.getUpLocator() )
                aimLocators.append( feather.getAimLocator() )
            
            # guide deformers
            if len(feathers) > 0:
                bend1Guides.append( feathers[0].getBend1()[0] )
                bend1Guides.append( feathers[-1].getBend1()[0] )
                bend2Guides.append( feathers[0].getBend2()[0] )
                bend2Guides.append( feathers[-1].getBend2()[0] )
                #twistGuides.append( feathers[0].getTwist()[0] )
                #twistGuides.append( feathers[-1].getTwist()[0] )
                latticeGuides.append( feathers[0].latticeData.lattice )
                latticeGuides.append( feathers[-1].latticeData.lattice )
        
        self._bend1sSet = pm.sets( bend1s, name=self._wingName + '_deformers_bend1sSet' )
        self._bend2sSet = pm.sets( bend2s, name=self._wingName + '_deformers_bend2sSet' )
        #self._twistsSet = pm.sets( twists, name=self._wingName + '_deformers_twistsSet' )
        self._latticesSet = pm.sets( lattices, name=self._wingName + '_deformers_latticesSet' )
        
        self._guideBend1sSet = pm.sets( bend1Guides, name=self._wingName + '_deformers_bend1GuidesSet' )
        self._guideBend2sSet = pm.sets( bend2Guides, name=self._wingName + '_deformers_bend2GuidesSet' )
        #self._guideTwistsSet = pm.sets( twistGuides, name=self._wingName + '_deformers_twistGuidesSet' )
        self._guideLatticesSet = pm.sets( latticeGuides, name=self._wingName + '_deformers_latticeGuidesSet' )
        
        self._upLocatorsSet = pm.sets( upLocators, name=self._wingName + '_locators_upLocatorsSet' )
        self._aimLocatorsSet = pm.sets( aimLocators, name=self._wingName + '_locators_aimLocatorsSet' )
        
        # TODO: create the character set






########################################################################
class WingPartNew(object):
    """the new wing part object
    """

    #----------------------------------------------------------------------
    def __init__(self):
        self._joints = []
        self._rootFollicles = []
        self._name = ''
        
        self._length = 10
        self._direction = 1
        
        self._hairSystem = None
        
        self._profileConstraintGroups = []
        self._profileAimLocators = []
        self._profileUpLocators = []
        self._staticProfileCurves = []
        
        self._smartBendControllers = []
        
        self._dynamicProfileCurves = []
        self._dynamicProfileCurvesGroup = None
        
        self._blendedProfileCurves = []
        self._blendedProfileCurvesGroup = None
        self._profileCurveBlendShapes = []
        
        self._rootCurve = None
        self._tipCurve = None
        self._tipJoints = []
    
    
    
    #----------------------------------------------------------------------
    def doSetup(self):
        """does the setup thing
        """
        
        # static profile curves
        # profile controllers
        # profile deformers --> deformed profile curves
        # dynamic profile curves
        # profiel blender
        # wingPartSurface
        # wingPartClosedSurface
        # poseDeformer
        
        # do the setup
        self.createProfileConstraintGroups()
        #self.createTipJoints()
        #self.createTipCurve()
        #self.rigTipCurve()
        #self.createProfileAimTargets()
        #self.createStaticProfileCurves()
        #self.createDynamicProfileCurves()
        #self.createBlendedProfileCurves()
        #self.createWingOpenSurface()
        
        
    
    
    
    #----------------------------------------------------------------------
    def joints():
        
        def fget(self):
            return self._joints
        
        def fset(self, joints):
            self._joints = joints
        
        return locals()
    
    joints = property( **joints() )
    
    
    
    #----------------------------------------------------------------------
    def direction():
        
        def fget(self):
            return self._direction
        
        def fset(self, direction):
            self._direction = direction
        
        return locals()
    
    direction = property( **direction() )
    
    
    
    #----------------------------------------------------------------------
    def hairSystem():
        
        def fget(self):
            return self._hairSystem
        
        def fset(self, hairSystem):
            self._hairSystem = hairSystem
        
        return locals()
    
    hairSystem = property( **hairSystem() )
    
    
    
    #----------------------------------------------------------------------
    def length():
        
        def fget(self):
            return self._length
        
        def fset(self, length):
            self._length = length
        
        return locals()
    
    length = property( **length() )
    
    
    
    #----------------------------------------------------------------------
    def name():
        
        def fget(self):
            return self._name
        
        def fset(self, name):
            self._name = name
        
        return locals()
    
    name = property( **name() )
    
    
    
    #----------------------------------------------------------------------
    def rootCurve():
        
        def fget(self):
            return self._rootCurve
        
        def fset(self, rootCurve):
            self._rootCurve = rootCurve
        
        return locals()
    
    rootCurve = property( **rootCurve() )
    
    
    
    #----------------------------------------------------------------------
    def rootFollicles():
        
        def fget(self):
            return self._rootFollicles
        
        def fset(self, rootFollicles):
            self._rootFollicles = rootFollicles
        
        return locals()
    
    rootFollicles = property( **rootFollicles() )
        
    
    
    
    #----------------------------------------------------------------------
    def createProfileConstraintGroups(self):
        """creates a group on every follicle that is going to be a parent for
        other objects
        """
        
        for i,follicle in enumerate(self._rootFollicles):
            # clear the seelction
            pm.select(None)
            
            # create an empty group node
            constraintGroup = pm.group(em=True)
            
            # parent it under the corresponding joint
            pm.parent( constraintGroup, self._joints[i] )
            
            # zero the transforms
            oyAux.goHome( constraintGroup )
            
            #then parent it under the follicle
            pm.parent( constraintGroup, follicle )
            
            # rename the constraintGroup
            constraintGroup.rename( self._name + '_wingProfileConstraintGroup#' )
            
            # create an axial correction group
            oyAux.createAxialCorrectionGroup( constraintGroup )
            
            # store it
            self._profileConstraintGroups.append( constraintGroup )
    
    
    
    #----------------------------------------------------------------------
    def createProfileAimTargets(self):
        """creates the controllers and let the transform parents to aim that
        controller
        """
        
        # for every constraint group create a locator
        for constraintGroup in self._profileConstraintGroups:
            aimLocator = pm.spaceLocator()
            upLocator = pm.spaceLocator()
            
            # rename them
            aimLocator.rename( self._name + '_aimTarget#' )
            upLocator.rename( self._name + '_upVector#' )
            
            # parent it under the parent of the current constraintGroup
            pm.parent( aimLocator, constraintGroup.getParent(), r=True )
            pm.parent( upLocator, constraintGroup.getParent(), r=True )
            
            # move it to the tip of the wing
            aimLocator.setAttr('tz', self._length * self._direction)
            upLocator.setAttr('ty', -self._direction )
            
            # create a projection of it
            projectedLocator = Utility.createProjection( aimLocator, (0,self._direction,0), constraintGroup.getParent() )
            
            # aim constraintGroup to the projectedLocator
            pm.aimConstraint( projectedLocator,
                              constraintGroup,
                              mo=False,
                              w=1,
                              aimVector=(0,0,1),
                              upVector=(0,1,0),
                              worldUpObject=upLocator,
                              worldUpType='object'
                              )
            
            # axial correction
            oyAux.createAxialCorrectionGroup( aimLocator )
            
            # store it
            self._profileAimLocators.append( aimLocator )
            self._profileUpLocators.append( upLocator )
        
        aimCount = len( self._profileAimLocators )
        # attach all the aim locators to the curve
        for i, aimLoc in enumerate( self._profileAimLocators ):
            Utility.attachToCurve( aimLoc,
                                   self._tipCurve,
                                   float(i)/float(aimCount-1),
                                   True )
    
    
    
    #----------------------------------------------------------------------
    def createTipJoints(self):
        """creates the tip joints
        """
        
        ## create three joints at the start, middle and end
        ## of the tip curve
        
        ## create a locator and attach it to the curve and query its
        ## position as it is moving to the end of the curve
        
        #startPosLoc = pm.spaceLocator()
        #midPosLoc = pm.spaceLocator()
        #endPosLoc = pm.spaceLocator()
        
        #Utility.attachToCurve( startPosLoc, self._tipCurve, param=0.0)
        #Utility.attachToCurve( midPosLoc, self._tipCurve, param=0.5)
        #Utility.attachToCurve( endPosLoc, self._tipCurve, param=1.0)
        
        ## query values of 0.0, 0.5, 1.0
        #startPos = pm.xform( startPosLoc, q=1, ws=1, t=1 )
        #midPos = pm.xform( midPosLoc, q=1, ws=1, t=1 )
        #endPos = pm.xform( endPosLoc, q=1, ws=1, t=1 )
        
        ## create three joints at these positions
        #startJoint = pm.joint( p=startPos )
        #midJoint = pm.joint( p=midPos )
        #endJoint = pm.joint( p=endPos )
        
        startJoint = pm.joint()
        pm.parent( startJoint, self._joints[0] )
        startJoint.setAttr('t', (0, 0, self._length) )
        self._tipJoints.append( startJoint )
        pm.select(None)
        
        middleJoint = pm.joint()
        pm.parent( middleJoint, self._joints[2] )
        middleJoint.setAttr('t', (0, 0, self._length) )
        self._tipJoints.append( middleJoint )
        pm.select(None)
        
        endJoint = pm.joint()
        pm.parent( endJoint, self._joints[4] )
        endJoint.setAttr('t', (self._length, 0, 0) )
        self._tipJoints.append( endJoint )
        pm.select(None)
    
    
    
    #----------------------------------------------------------------------
    def createTipCurve(self):
        """creates the tip curve
        """
        
        #tPA = Utility.createThreePointArc( self._tipJoints[0],
                                           #self._tipJoints[1],
                                           #self._tipJoints[2],
                                           #False )
        
        #curveShape = pm.createNode( 'nurbsCurve' )
        #tPA.attr('outputCurve') >> curveShape.attr('create')
        
        #curveShape.attr('create').disconnect()
        #pm.delete(tPA)
        
        #self._tipCurve = curveShape.getParent()
        
        # duplicate the root curve and scale it to 1,0,0
        temp = pm.duplicate( self._rootCurve )
        self._tipCurve = temp[0]
        
        pm.parent ( self._tipCurve, self._joints[0] )
        self._tipCurve.setAttr('s', (1,0,0) )
        
        pm.makeIdentity( self._tipCurve, apply=1, t=1, r=1, s=1)
        
        self._tipCurve.setAttr('t', (0,0,self._length) )
        
        pm.parent( self._tipCurve, w=1 )
    
    
    
    #----------------------------------------------------------------------
    def rigTipCurve(self):
        """rigs the tip curve with three joint
        """
        
        
        # rig the curve
        pm.select( self._tipJoints[0],
                   self._tipJoints[1],
                   self._tipJoints[2],
                   self._tipCurve )
        
        pm.skinCluster( tsb=True, dr=1.0 )
    
    
    
    #----------------------------------------------------------------------
    def createStaticProfileCurves(self):
        """creates the wing profile curves which are owned by the wing itself
        """
        # create a profile curve in the origin then parent a duplicate to the
        # follicle
        
        CVPositions = [(0,0,0),
                       (0,0,0.3333 * self._length),
                       (0,0,0.6667 * self._length),
                       (0,0,1.0 * self._length)
                       ]
        
        baseProfileCurve = Utility.createCurve( CVPositions )
        
        for i, constraintGroup in enumerate(self._profileConstraintGroups):
            # duplicate the curve
            dupCurve = pm.duplicate( baseProfileCurve )[0]
            
            # parent it under the transform parent
            pm.parent( dupCurve, constraintGroup )
            
            # zero transforms
            oyAux.goHome( dupCurve )
            
            # rename it
            dupCurve = pm.rename( dupCurve, self._name + '_staticProfileCurve#' )
            
            # store it
            self._staticProfileCurves.append( dupCurve )
            
            # create a smartBend deformer
            smartBendController, smartBendXform = Utility.smartBend(dupCurve, [0,0,1])
            pm.parent( smartBendController, self._profileAimLocators[i] )
            oyAux.goHome( smartBendController )
            self._smartBendControllers.append( smartBendController )
            
            # parent the bend xform to the parent of the constraintGroup
            pm.parent( smartBendXform, constraintGroup )
        
        # delete the baseProfileCurve
        pm.delete( baseProfileCurve )
    
    
    
    #----------------------------------------------------------------------
    def createWingOpenSurface(self):
        """creates the open wing surface
        """
        
        # create a surface by lofting the profileCurves
        self._openSurface = Utility.createSurfaceFromCurves( self._blendedProfileCurves, True )
        self._openSurface.rename( self._name + '_wingOpenSurface#')
    
    
    
    #----------------------------------------------------------------------
    def createDynamicProfileCurves(self):
        """these are the duplicate of the profileCurves which are then
        converted to hairs
        """
        
        # also connect them to the rootFollicles
        # and make the follicles dynamic
        
        # create the dynamicProfileCurvesGroup
        pm.select(None)
        self._dynamicProfileCurvesGroup = pm.group(em=True)
        self._dynamicProfileCurvesGroup.rename( self._name + '_wingDynamicProfileCurvesGroup' )
        
        for curve,follicle in zip(self._staticProfileCurves, self._rootFollicles):
            # convert the profileCurves to dynamic curves
            dynamicCurve = Utility.convertToHair( curve, follicle, self._hairSystem, False )
            
            # parent it
            pm.parent( dynamicCurve, self._dynamicProfileCurvesGroup )
            
            # rename it
            dynamicCurve.rename( self._name + '_wingDynamicCurve#' )
            
            # add it to the list
            self._dynamicProfileCurves.append( dynamicCurve )
    
    
    
    #----------------------------------------------------------------------
    def createBlendedProfileCurves(self):
        """creates the blendedProfileCurves (obviously)
        
        which are then used to create the wingSurface
        """
        
        # create the blendedProfileCurves group
        self._blendedProfileCurvesGroup = pm.group( em=True, n=(self._name + '_wingBlendedProfileCurveGroup#'))
        
        for staticCurve, dynamicCurve in zip( self._staticProfileCurves, self._dynamicProfileCurves ):
            
            # create a duplicate of the profileCurve
            blendedCurve = pm.duplicate( staticCurve )[0]
            
            # rename the curve
            blendedCurve.rename( self._name + '_wingBlendedProfileCurve#' )
            
            # store the curve
            self._blendedProfileCurves.append( blendedCurve )
            
            # create a blendShape between profileCurve, dynaCurve and
            pm.select( staticCurve, dynamicCurve, blendedCurve )
            
            blendShape = pm.blendShape( foc=True, o='world' )[0]
            
            # set the weight of the profileCurve to 1
            blendShape.setAttr( dynamicCurve.name(), 1 )
            
            # store the blendShape
            self._profileCurveBlendShapes.append( blendShape )
            
            # parent the curve under the blendedProfileCurvesGroup
            pm.parent( blendedCurve, self._blendedProfileCurvesGroup )
            
            # set them to zero
            oyAux.goHome( blendedCurve )






########################################################################
class WingPart(object):
    """a little helper class to hold partial wing data, like the wing joint,
    the tip joint for that wing joint and feather root and tip follicles
    """
    
    #----------------------------------------------------------------------
    def __init__(self):
        
        self._rootJoint = None
        
        self._rootFollicleParameters = []
        
        self._rootFollicles = []
        #self._rootFollicleTransforms = []
        self._rootFollicleCount = 0
        
        self._wingRootSurface = None
        self._wingRootCurve = None
        
        self._wingLength = 0
        
        self._direction = 1 # by default
        
        self._featherCount = 0
        
        self._featherBaseObject = None
        
        self._feathers = []
        self._upperCovertFeathers = [] # the second layer
        self._lowerCovertFeathers = []
        self._upperGreaterCovertFeathers = [] # the third layer
        self._lowerGreaterCovertFeathers = [] # the third layer
        
        self._openWingJoint = None
        self._closedWingJoint = None
    
    
    
    #----------------------------------------------------------------------
    def appendTipFollicle(self, follicle, follicleParent):
        """appends a new tip follicle to the list
        """
        
        self._tipFollicles.append( follicle )
        self._tipFollicleParents.append( follicleParent )
        self._tipFollicleParameters.append( [follicle.getAttr('pu'), follicle.getAttr('pv') ] )
    
    
    
    #----------------------------------------------------------------------
    def appendRootFollicle(self, follicle):
        """appends a new root follicle to the list
        """
        
        self._rootFollicles.append( follicle )
        follicleShape = follicle.getShape()
        self._rootFollicleParameters.append( [follicleShape.getAttr('pu'), follicleShape.getAttr('pv') ] )
    
    
    
    #----------------------------------------------------------------------
    def setRootJoint(self, joint):
        """sets the wing joint
        """
        self._rootJoint = joint
        
        # update the wing length
        self._wingLength = Utility.getJointLength( self._rootJoint )
        
        if self._wingLength < 0:
            self._direction = -1
        if self._wingLength == 0:
            # get the direction from the joint x position
            if joint.getAttr('tx') < 0:
                self._direction = -1
    
    
    
    #----------------------------------------------------------------------
    def getRootJoint(self):
        """returns the root joint
        """
        return self._rootJoint
    
    
    
    #----------------------------------------------------------------------
    def getRootFollicles(self):
        """returns the root follicles
        """
        return self._rootFollicles
    
    
    
    #----------------------------------------------------------------------
    def setRootFollicles(self, rootFollicles):
        """sets the root follicles
        """
        self._rootFollicles = rootFollicles
    
    
    
    #----------------------------------------------------------------------
    def getTipFollicles(self):
        """returns the tip follicles
        """
        return self._tipFollicles
    
    
    
    #----------------------------------------------------------------------
    def setTipFollicles(self, tipFollicles):
        """sets the tip follicles
        """
        self._tipFollicles = tipFollicles
    
    
    
    #----------------------------------------------------------------------
    def setWingRootCurve(self, curve):
        """sets the root curve
        """
        self._wingRootCurve = curve
    
    
    
    #----------------------------------------------------------------------
    def getRootCurve(self):
        """returns the root curve
        """
        return self._rootCurve
    
    
    
    #----------------------------------------------------------------------
    def setTipCurve(self, curve):
        """sets the tip curve
        """
        self._tipCurve = curve
    
    
    
    #----------------------------------------------------------------------
    def getTipJoint(self):
        """returns the tip joint
        """
        return self._tipJoint
    
    
    
    #----------------------------------------------------------------------
    def setTipJoint(self, joint):
        """sets the tip joint
        """
        self._tipJoint = joint
    
    
    
    #----------------------------------------------------------------------
    def setRootFollicles(self, follicles):
        """sets the root follicles
        """
        self._rootFollicles = follicles
        self._rootFollicleCount = len( follicles )
    
    
    
    #----------------------------------------------------------------------
    def setTipFollicles(self, follicles):
        """set the tip follicles
        """
        self._tipFollicles = follicles
        self._tipFollicleCount = len( follicles )
    
    
    
    #----------------------------------------------------------------------
    def setWingRootSurface(self, surface):
        """sets the root surface
        """
        self._wingRootSurface = surface
    
    
    
    #----------------------------------------------------------------------
    def getWingLength(self):
        """returns teh wing length
        """
        return self._wingLength
    
    
    
    #----------------------------------------------------------------------
    def setWingLength(self, wingLength):
        """sets the current partial wing length
        """
        self._wingLength = wingLength
    
    
    
    #----------------------------------------------------------------------
    def setWingDirection(self, direction):
        """sets the wing direction, for wings that the child lies on the
        negative axis the direction is negative
        """
        if direction < 0:
            self._direction = -1
    
    
    
    #----------------------------------------------------------------------
    def getFeatherCount(self):
        """returns the feather count
        """
        return self._featherCount
    
    
    
    #----------------------------------------------------------------------
    def setFeatherCount(self, featherCount):
        """sets the feather count over that part of the wing
        """
        # do not allow negative numbers
        self._featherCount = max(0,featherCount)
    
    
    
    #----------------------------------------------------------------------
    def feathers():
        
        def fget(self):
            return self._feathers
        
        def fset(self, feathers):
            self._feathers = feathers
        
        return locals()
    
    feathers = property( **feathers() )
    
    
    
    #----------------------------------------------------------------------
    @property
    def upperProfileCurves(self):
        """returns the upper profile curves
        """
        return self._upperProfileCurves
    
    
    
    #----------------------------------------------------------------------
    @property
    def lowerProfileCurves(self):
        """returns the lower profile curves
        """
        return self._lowerProfileCurves
    
    
    
    #----------------------------------------------------------------------
    @property
    def upperCovertFeathers(self):
        """returns upper covert feathers
        """
        return self._upperCovertFeathers
    
    
    
    #----------------------------------------------------------------------
    @property
    def lowerCovertFeathers(self):
        """returns lower covert feathers
        """
        return self._lowerCovertFeathers
    
    
    
    #----------------------------------------------------------------------
    @property
    def upperGreaterCovertFeathers(self):
        """returns upper greater covert feathers
        """
        return self._upperGreaterCovertFeathers
    
    
    
    #----------------------------------------------------------------------
    @property
    def lowerGreaterCovertFeathers(self):
        """returns lower greater covert feathers
        """
        return self._lowerGreaterCovertFeathers
    
    
    
    #----------------------------------------------------------------------
    def openTipJoint():
        
        def fget(self):
            return self._openTipJoint
        
        def fset(self, openTipJoint):
            self._openTipJoint = openTipJoint
        
        return locals()
    
    openTipJoint = property( **openTipJoint() )
    
    
    
    #----------------------------------------------------------------------
    def _getClosedTipJoint(self):
        """get the closed tip joint
        """
        return self._closedTipJoint
    
    
    
    #----------------------------------------------------------------------
    def _setClosedTipJoint(self, joint):
        """set the closed tip joint
        """
        self._closedTipJoint = joint
    
    closedTipJoint = property( _getClosedTipJoint, _setClosedTipJoint )
    
    
    
    #----------------------------------------------------------------------
    def _getOpenWingJoint(self):
        """get the open wing joint
        """
        return self._openWingJoint
    
    
    
    #----------------------------------------------------------------------
    def _setOpenWingJoint(self, joint):
        """set the open wing joint
        """
        self._openWingJoint = joint
    
    openWingJoint = property( _getOpenWingJoint, _setOpenWingJoint )
    
    
    
    #----------------------------------------------------------------------
    def _getClosedWingJoint(self):
        """get the closed wing joint
        """
        self._closedWingJoint
    
    
    
    #----------------------------------------------------------------------
    def _setClosedWingJoint(self, joint):
        """set the closed wind joint
        """
        self._closedWingJoint = joint
    
    closedWingJoint = property( _getClosedWingJoint, _setClosedWingJoint )
    
    
    
    #----------------------------------------------------------------------
    def setFeatherBaseObject(self, featherBaseObject):
        """sets the feather object
        """
        self._featherBaseObject = featherBaseObject
    
    
    
    #----------------------------------------------------------------------
    def getDirection(self):
        """returns the direction
        """
        return self._direction
    
    
    
    #----------------------------------------------------------------------
    def createFeatherRootFollicles(self):
        """creates the root follicle
        """
        
        if self._featherCount < 1:
            return
        
        # create a locator
        rootLocator = pm.spaceLocator()
        
        # duplicate it over the wing joint
        newLocators = Utility.duplicateOverJoint( self._rootJoint, rootLocator, self._featherCount )
        
        for locator in newLocators:
            data = Utility.closestPointOnSurface( locator, self._rootSurface )
            self._rootFollicleParameters.append( data )
        
        self._rootFollicleCount = len(self._rootFollicleParameters)
        
        # delete the locators
        pm.delete( newLocators, rootLocator )
        
        # use the data to create hair follicles
        self._rootFollicles = []
        
        for i in range( self._rootFollicleCount ):
            newFollicle = Utility.createFollicle( self._rootSurface, self._rootFollicleParameters[i][1], self._rootFollicleParameters[i][2] )
            
            self._rootFollicles.append( newFollicle )
        
        pm.select(None)
    
    
    
    #----------------------------------------------------------------------
    def createFeathers(self):
        """creates the feathers
        """
        
        # duplicate the featherBaseObject
        # and parent them under the follicles
        
        self.featherObjects = []
        
        featherCount = self._featherCount
        
        self._feathers = []
        
        if featherCount < 1:
            #featherCount = 1
            return
        
        
        for i in range(featherCount):
            # create a new Feather object
            feather = Feather(self, self._rootFollicleTransforms[i], self._tipFollicleParents[i], self._featherBaseObject)
            feather.direction = self._direction
            feather.create()
            
            self._feathers.append( feather )
            
            if i != 0:
                feather.prevFeather = self._feathers[i-1]
        
        for i in range(0,featherCount-1):
            self._feathers[i].nextFeather = self._feathers[i+1]
        
        # the first feather should be a guide feather
        self._feathers[0].isGuide = True
        
        # create covert feathers
        # second layer
        self._upperCovertFeathers = self.createCovertFeathers( 0.5, 0.5 )
        self._lowerCovertFeathers = self.createCovertFeathers( -0.5, 0.5 )
        
        # third layer
        self._upperGreaterCovertFeathers = self.createCovertFeathers( 0.75, 0.25 )
        self._lowerGreaterCovertFeathers = self.createCovertFeathers( -0.75, 0.25 )
    
    
    
    #----------------------------------------------------------------------
    def createProfileCurves(self):
        """creates the profile curves for upper and lower part of the wing
        """
        
        # just create curves in 10 unit length and add them to the bend2 deformer
        
        # create a curve pointing to +Z direction then parent it under
        # feathers transform parent
        
        self._upperProfileCurves = []
        self._lowerProfileCurves = []
        
        # create cvPoint positions
        curveCVPos = []
        for i in range(11):
            curveCVPos.append( (0,0,i) )
        
        for i in range(self._featherCount):
            # the feather
            feather = self._feathers[i]
            
            assert(isinstance(feather, Feather))
            
            # get the transform parent of the feather
            transformParent = feather.getTransformParent()
            
            # the upper profile curve
            upperProfileCurve = Utility.createCurve( curveCVPos )
            upperProfileCurve.rename( 'upperProfileCurve#' )
            
            # the lower profile curve
            lowerProfileCurve = Utility.createCurve( curveCVPos )
            lowerProfileCurve.rename( 'lowerProfileCurve#' )
            
            # parent them under the transform parent
            pm.parent( upperProfileCurve, transformParent, r=True )
            pm.parent( lowerProfileCurve, transformParent, r=True )
            
            # attach them to the bend and twist deformers
            upperProfileCurveShape = upperProfileCurve.getShape()
            lowerProfileCurveShape = lowerProfileCurve.getShape()
            
            bend1 = feather.getBend1()[0]
            pm.nonLinear( bend1, edit=True, geometry=upperProfileCurveShape )
            pm.nonLinear( bend1, edit=True, geometry=lowerProfileCurveShape )
            
            #twist = feather.getTwist()[0]
            #pm.nonLinear( twist, edit=True, geometry=upperProfileCurveShape )
            #pm.nonLinear( twist, edit=True, geometry=lowerProfileCurveShape )
            lattice = pm.Lattice( feather.latticeData.lattice.getShape() )
            lattice.setGeometry( upperProfileCurveShape )
            lattice.setGeometry( lowerProfileCurveShape )
            
            bend2 = feather.getBend2()[0]
            pm.nonLinear( bend2, edit=True, geometry=upperProfileCurveShape )
            pm.nonLinear( bend2, edit=True, geometry=lowerProfileCurveShape )
            
            # save the curves
            self._upperProfileCurves.append( upperProfileCurve )
            self._lowerProfileCurves.append( lowerProfileCurve )
    
    
    
    #----------------------------------------------------------------------
    def createCovertFeathers(self, offset = 0.0, scale=1.0):
        """creates the second layer of feathes in the upper part
        """
        # duplicate the featherBaseObject
        # and parent them under the follicles
        
        featherCount = self._featherCount
        
        if featherCount == 0:
            #featherCount = 1
            return
        
        featherArray = []
        
        for i in range(featherCount):
            # create a new Feather object
            #covertFeather = Feather(self, self._rootFollicleTransforms[i], self._tipFollicleParents[i], self._featherBaseObject, self._feathers[i].upLocator, self._feathers[i].aimLocator, self._feathers[i].bend1, self._feathers[i].bend2, self._feathers[i].twist, self._feathers[i].transformParent, self._direction )
            covertFeather = Feather(self, self._rootFollicleTransforms[i], self._tipFollicleParents[i], self._featherBaseObject, self._feathers[i] )
            covertFeather.create()
            
            featherArray.append( covertFeather )
            
            if i != 0:
                covertFeather.prevFeather = featherArray[i-1]
            
            # move the feathers a little bit higher
            covertFeather.featherObject.setAttr('ty', offset)
            covertFeather.featherObject.setAttr('s', (scale,scale,scale))
        
        for i in range(0,featherCount-1):
            featherArray[i].nextFeather = featherArray[i+1]
        
        # the first feather should be a guide feather
        featherArray[0].isGuid = True
        
        return featherArray
    
    
    
    #----------------------------------------------------------------------
    def connectTipJointsToRootJoints(self):
        """connects the tip joint to root joint by aim constraining the tip
        to root joint and then parenting it under the root joint
        """
        # create the aim constraints between open and closed joints
        pm.aimConstraint( self._openWingJoint, self._openTipJoint, offset=(0,0,0), weight=1, aimVector=(0, 0, -self._direction), upVector=(0, -self._direction, 0), worldUpType='objectrotation', worldUpObject=self._openWingJoint, worldUpVector=(0,-self._direction,0) )
        pm.aimConstraint( self._closedWingJoint, self._closedTipJoint, offset=(0,0,0), weight=1, aimVector=(0, 0, -self._direction), upVector=(0, -self._direction, 0), worldUpType='objectrotation', worldUpObject=self._closedWingJoint, worldUpVector=(0,-self._direction,0) )
        
        # parent them to their roots
        pm.parent( self._openTipJoint, self._openWingJoint)
        pm.parent( self._closedTipJoint, self._closedWingJoint )
        pm.parent( self._tipJoint, self._rootJoint )
        
        # create axial correction groups for the tip joints
        oyAux.createAxialCorrectionGroup( self._openTipJoint )
        oyAux.createAxialCorrectionGroup( self._closedTipJoint )
        oyAux.createAxialCorrectionGroup( self._tipJoint )
    
    
    
    #----------------------------------------------------------------------
    def createFeatherBlenders(self):
        """creates blend nodes to blend guide feathers attributes over
        the other feathers
        """
        
        # get all the deformers
        bend1 = []
        bend2 = []
        #twist = []
        latticeData = []
        
        for i in range(self._featherCount):
            bend1.append( self._feathers[i].bend1[0] )
            bend2.append( self._feathers[i].bend2[0] )
            #twist.append( self._feathers[i].twist[0] )
            latticeData.append( self._feathers[i].latticeData )
            
        
        # create blender for bend1
        Utility.groupBlendAttributes( bend1, 'curvature' )
        Utility.groupBlendAttributes( bend2, 'curvature' )
        #Utility.groupBlendAttributes( twist, 'startAngle' )
        #Utility.groupBlendAttributes( twist, 'endAngle' )
    
    
    
    ##----------------------------------------------------------------------
    #def createFeatherBlenders(self):
        #"""creates blendTwoAttribute nodes between the first and last feather
        #of the current WingPart object
        #"""
        
        #for i in range(self._featherCount):
            ## the weight of 
        






########################################################################
class Feather(object):
    """A feather class that helps to setup feather objects
    """
    
    
    
    #----------------------------------------------------------------------
    #def __init__(self, parentWingPart=None, parentFollicle=None, aimFollicle=None, sourceObject=None, upLocator=None, aimLocator=None, bend1=None, bend2=None, lattice=None, transformParent=None, direction=1):
    def __init__(self, parentWingPart=None, parentFollicle=None, aimFollicle=None, sourceObject=None, parentFeather=None ):
        
        self._bend1 = None
        self._bend2 = None
        #self._lattice = None
        #self._latticeJoints = []
        #self._latticeMiddleJoint
        #self._latticeSkinCluster = None
        self._latticeData = LatticeData()
        
        self._parentWingPart = parentWingPart
        self._parentFollicle = parentFollicle
        
        self._aimFollicle = aimFollicle
        self._aimConstraintNode = None
        
        self._length = 0
        
        self._prevFeather = None
        self._nextFeather = None
        
        self._featherObject = None
        self._featherSourceObject = sourceObject
        
        self._upLocator = None
        self._aimLocator = None
        
        self._isGuideFeather = False
        
        self._transformParent = None
        
        self._direction = 1
        
        # if there is a parent feather use its deformers
        if parentFeather is not None:
            assert( isinstance(parentFeather, Feather) )
            self._bend1 = parentFeather.bend1
            self._bend2 = parentFeather.bend2
            
            self._latticeData = parentFeather.latticeData
            
            self._length = parentFeather.length
            self._upLocator = parentFeather.upLocator
            self._aimLocator = parentFeather.aimLocator
            self._transformParent = parentFeather.transformParent
            self._direction = parentFeather.direction
    
    
    
    
    #----------------------------------------------------------------------
    def create(self):
        """creates the feather
        """
        
        # if there is no data do nothing
        if self._parentWingPart == None or self._parentFollicle == None or self._featherSourceObject == None or self._aimFollicle == None:
            return
        
        # duplicate the base object
        temp = self.featherSourceObject.duplicate(rc=True)
        
        self._featherObject = temp[0]
        
        if self._transformParent == None:
            pm.select(None)
            self._transformParent = pm.group(n='transformParent#')
            
            # parent the transformParent under the follicle
            pm.parent( self._transformParent, self._parentFollicle, r=True )
        
        # parent the feather to transform parent
        pm.parent( self._featherObject, self._transformParent, r=True)
        
        # rotate the feather if the direction is -1
        if self._direction == -1:
            self._featherObject.setAttr('rz',180)
        
        # create up locator
        if self._upLocator == None:
            self.createUpLocator()
        
        if self._aimLocator == None:
            self.createAimLocator()
        
        # create aim constraint
        self.createAimConstraint()
        
        # create the deformers
        self.createDeformers()
    
    
    
    #----------------------------------------------------------------------
    def createAimConstraint(self):
        """creates the aim constraint
        """
        if self._parentFollicle == None or self._aimLocator == None or self._transformParent == None:
            return
        
        # create the aim constraint
        self.aimConstraintNode = pm.aimConstraint( self._aimLocator, self._transformParent, offset=(0,0,0), weight=1, aimVector=(0,0,1), upVector=(0,1,0), worldUpType='object', worldUpObject=self._upLocator, worldUpVector=(0,0,1) )
    
    
    
    #----------------------------------------------------------------------
    def createUpLocator(self):
        """creates the up vector locator
        """
        
        self._upLocator = pm.spaceLocator()
        
        self._upLocator.getShape().setAttr('localScale',(0.25,0.25,0.25))
        
        # parent it to the parent follicle
        pm.parent( self._upLocator, self._parentFollicle, r=True )
        pm.select(None)
        #pm.parent( self._upLocator, self._parentWingPart.getRootJoint(), r=False )
        
        # move it a little bit up
        #self._upLocator.setAttr('tz',1)
        pm.move(self._upLocator,(0,0,1),os=True,r=True)
        
        # create an axial correction group for the up locator
        oyAux.createAxialCorrectionGroup(self._upLocator)
    
    
    
    #----------------------------------------------------------------------
    def createAimLocator(self):
        """creates the aim vector locator
        """
        
        self._aimLocator = pm.spaceLocator()
        
        self._aimLocator.getShape().setAttr('localScale',(0.25,0.25,0.25))
        
        # parent it to the aim follicle
        pm.select(None)
        pm.parent( self._aimLocator, self._aimFollicle, r=True )
    
    
    
    #----------------------------------------------------------------------
    def createDeformers(self):
        """creates the feather deformers, which are two bend and one
        lattice deformer
        """
        
        #[0] - the deformer
        #[1] - the handle
        # bend1
        pm.select(None)
        if self._bend1 == None:
            bend1 = pm.nonLinear( self._featherObject.getShape(), type='bend', defaultScale=True)
            # parent it under self._transformParent
            pm.parent( bend1[1], self._transformParent )
            #adjust settings and position
            bend1[0].setAttr('lowBound',-10)
            bend1[0].setAttr('highBound',10)
            bend1[1].setAttr('t',(0,0,0))
            bend1[1].setAttr('r',(0,0,90))
            self._bend1 = bend1
        else:
            # append the object to the given bend deformer
            pm.nonLinear( self._bend1[0], edit=True, geometry=self._featherObject.getShape() )
        
        ## twist
        #pm.select(None)
        #if self._twist == None:
            #twist = pm.nonLinear( self._featherObject.getShape(), type='twist', defaultScale=True)
            ## parent it under self._transformParent
            #pm.parent( twist[1], self._transformParent )
            ##adjust settings and position
            #twist[0].setAttr('lowBound', 0)
            #twist[0].setAttr('highBound', 10)
            #twist[0].setAttr('startAngle', 0)
            #twist[1].setAttr('t', (0,0,0))
            #twist[1].setAttr('r', (90,0,0))
            #self._twist = twist
        #else:
            ## append the object to the given twist deformer
            #pm.nonLinear( self._twist[0], edit=True, geometry=self._featherObject.getShape() )
        
        # ---------------------------------------------------------------------
        # new system changes 
        # instead of twist use a lattice that is deformed by joints
        # ---------------------------------------------------------------------
        pm.select(None)
        if self._latticeData.lattice == None:
            lattice = pm.lattice( self._featherObject.getShape(), oc=True )
            # base
            pm.parent( lattice[2], self._transformParent )
            self._latticeData.ffd = lattice[0]
            self._latticeData.lattice = lattice[1]
            self._latticeData.latticeBase = lattice[2]
            
            # rig the lattice
            self.createLatticeJoints()
            self.rigLatticePoints()
            
        else:
            # append the object to the given lattice deformer
            #self._lattice[1].setGeometry( self._featherObject.getShape() )
            self._latticeData.lattice.setGeometry( self._featherObject.getShape() )
        # ---------------------------------------------------------------------
        
        # bend2
        pm.select(None)
        if self._bend2 == None:
            bend2 = pm.nonLinear( self._featherObject.getShape(), type='bend', defaultScale=True)
            # parent it under self._transformParent
            pm.parent( bend2[1], self._transformParent )
            #adjust settings and position
            bend2[0].setAttr('lowBound',0)
            bend2[0].setAttr('highBound',10)
            bend2[1].setAttr('t',(0,0,0))
            bend2[1].setAttr('r',(90,0,90))
            self._bend2 = bend2
        else:
            # append the object to the given bend deformer
            pm.nonLinear( self._bend2[0], edit=True, geometry=self._featherObject.getShape() )
    
    
    
    #----------------------------------------------------------------------
    def createLatticeJoints(self):
        """creates the joints that deforms the lattice
        """
        # create two joints at the featherRoot and featherTip
        rootJoint = pm.Joint()
        middleJoint = pm.Joint()
        tipJoint = pm.Joint()
        
        # parent them under the transformParent
        pm.parent( rootJoint, self._transformParent )
        pm.parent( middleJoint, self._transformParent )
        pm.parent( tipJoint, self._transformParent )
        
        # move them to their location
        rootJoint.setAttr('t',(0,0,0))
        middleJoint.setAttr('t',(0,0,5))
        tipJoint.setAttr('t',(0,0,10))
        
        # create an axialCorrection group for the middle joint
        # and parent constraint it to the root and tip joints
        middleJointACGroup = oyAux.createAxialCorrectionGroup( middleJoint )
        
        # the parent constraint
        
        
        # save the joints as latticeJoints
        #self._latticeJoints.append( rootJoint )
        #self._latticeJoints.append( tipJoint )
        
        lattice_rootJoint = LatticeJoint()
        lattice_rootJoint.joint = rootJoint
        
        lattice_middleJoint = LatticeJoint()
        lattice_middleJoint.joint = middleJoint
        lattice_middleJoint.ACGroup = middleJointACGroup
        
        lattice_tipJoint = LatticeJoint()
        lattice_tipJoint.joint = tipJoint
        
        self._latticeData.rootJoint = lattice_rootJoint
        self._latticeData.middleJoint = lattice_middleJoint
        self._latticeData.tipJoint = lattice_tipJoint
    
    
    
    #----------------------------------------------------------------------
    def rigLatticePoints(self):
        """rigs the lattice points with two joints
        """
        
        joints = self._latticeData.getPureJoints()
        
        pm.select(None)
        pm.select( self._latticeData.lattice, joints )
        
        self._latticeSkinCluster = pm.skinCluster(
            mi=3,
            dr=2
        )
        
        pm.select(None)
    
    
    
    #----------------------------------------------------------------------
    def getParentFollicle(self):
        """get the parent follicles transform node
        """
        return self._parentFollicle
    
    
    
    #----------------------------------------------------------------------
    def setParentFollicle(self, parentFollicle):
        """set the parent follicles transform node
        """
        # TODO : parent to the new follicles transfrom node and zero out
        # transformation values
        self._parentFollicle = parentFollicle
    
    parentFollicle = property( getParentFollicle, setParentFollicle )
    
    
    #----------------------------------------------------------------------
    def getAimFollicle(self):
        """get the aim follicles transform node
        """
        return self._aimFollicle
    
    
    
    #----------------------------------------------------------------------
    def setAimFollicle(self, aimFollicle):
        """set the aim follicles transform node
        """
        # TODO : delete the current aimConstraint and recreate a new one
        self._aimFollicle = aimFollicle
    
    aimFollicle = property( getAimFollicle, setAimFollicle )
    
    
    
    #----------------------------------------------------------------------
    def getAimLocator(self):
        """gets the aim locator
        """
        return self._aimLocator
    
    
    
    #----------------------------------------------------------------------
    def setAimLocator(self, aimLocator):
        """sets the aim locator
        """
        
        self._aimLocator = aimLocator
    
    aimLocator = property( getAimLocator, setAimLocator )
    
    
    
    #----------------------------------------------------------------------
    def getAimConstraintNode(self):
        """get the aim constraint node
        """
        return self._aimConstraintNode
    
    
    
    #----------------------------------------------------------------------
    def setAimConstraintNode(self, aimConstraintNode):
        """set the aimConstraint node
        """
        # do nothing just store the node
        self._aimConstraintNode = aimConstraintNode
    
    aimConstraintNode = property( getAimConstraintNode, setAimConstraintNode )
    
    
    
    #----------------------------------------------------------------------
    def getFeatherObject(self):
        """get the featherObject
        """
        return self._featherObject
    
    
    
    #----------------------------------------------------------------------
    def setFeatherObject(self, featherObject):
        """set the featherObject
        """
        self._featherObject = featherObject
    
    featherObject = property( getFeatherObject, setFeatherObject )
    
    
    #----------------------------------------------------------------------
    def getFeatherSourceObject(self):
        """get the featherSourceObject
        """
        return self._featherSourceObject
    
    
    
    #----------------------------------------------------------------------
    def setFeatherSourceObject(self, featherSourceObject):
        """set the featherSourceObject
        """
        # TODO: replace the old object with deformers
        self._featherSourceObject = featherSourceObject
    
    featherSourceObject = property( getFeatherSourceObject, setFeatherSourceObject )
    
    
    #----------------------------------------------------------------------
    def getIsGuideFeather(self):
        """get the feather a guide feather
        """
        return self._isGuideFeather
    
    
    
    #----------------------------------------------------------------------
    def setIsGuideFeather(self, isGuideFeather):
        """set the feather a guide feather
        """
        # TODO: make the neccessary connections to let it guide the surrounding
        # feathers
        # go to the next and previous feathers until you find another guide
        # and make the connections to blend the attributes
        self.isGuideFeather = isGuideFeather
    
    isGuideFeather = property( getIsGuideFeather, setIsGuideFeather )
    
    
    #----------------------------------------------------------------------
    def getLength(self):
        """get the feather length
        """
        return self._length
    
    
    
    #----------------------------------------------------------------------
    def setLength(self, length):
        """set the feather length
        """
        # TODO : resize the objectand the deformers
        self.length = length
    
    length = property( getLength, setLength )
    
    
    
    #----------------------------------------------------------------------
    def getBend1(self):
        """get the bend1
        """
        return self._bend1
    
    
    #----------------------------------------------------------------------
    def setBend1(self, bend1):
        """sets the bend1 deformer
        """
        # TODO: adjust the bend handle to make it align to the x-axis of the
        # feather object
        self._bend1 = bend1
    
    bend1 = property( getBend1, setBend1 )
    
    
    
    #----------------------------------------------------------------------
    def getBend2(self):
        """get the bend2
        """
        return self._bend2
    
    
    #----------------------------------------------------------------------
    def setBend2(self, bend2):
        """sets the bend2 deformer
        """
        # TODO: adjust the bend handle to make it align to the z-axis of the
        # feather object
        self.bend2 = bend2
    
    bend2 = property( getBend2, setBend2 )
    
    
    
    #----------------------------------------------------------------------
    def getDirection(self):
        """get the direction
        """
        return self._direction
    
    
    
    #----------------------------------------------------------------------
    def setDirection(self, direction):
        """set the direction
        """
        self._direction = direction
        
        ## rotate the object if the direction is -1
        #if self._direction == -1:
            #self._featherObject.setAttr('rz',180)
    
    direction = property( getDirection, setDirection )
    
    
    
    ##----------------------------------------------------------------------
    #def getTwist(self):
        #"""get the twist deformer
        #"""
        #return self._twist
    
    
    
    ##----------------------------------------------------------------------
    #def setTwist(self, twist):
        #"""set the twist deformer
        #"""
        ## TODO : adjust the twist handle to make it align to the z-axis of the
        ## feather object
        #self._twist = twist
    
    #twist = property( getTwist, setTwist )
    
    ##----------------------------------------------------------------------
    #def getLattice(self):
        #"""returns the lattice deformer
        #"""
        #return self._lattice
    
    
    
    ##----------------------------------------------------------------------
    #def setLattice(self, lattice):
        #"""set the lattice deformer
        #"""
        #self._lattice = lattice
    
    #lattice = property( getLattice, setLattice )
    
    
    
    #----------------------------------------------------------------------
    def getLatticeData(self):
        """returns the lattice joints
        """
        return self._latticeData
    
    
    
    #----------------------------------------------------------------------
    def setLatticeData(self, latticeDataObj):
        """sets the lattice data object
        """
        self._latticeData = latticeDataObj
    
    latticeData = property( getLatticeData, setLatticeData )
    
    
    
    #----------------------------------------------------------------------
    def getUpLocator(self):
        """get the upLocator
        """
        return self._upLocator
    
    
    
    #----------------------------------------------------------------------
    def setUpLocator(self, upLocator):
        """sets the upLocator
        """
        
        # TODO: check if the old upLocator should be deleted or not
        
        self._upLocator = upLocator
        
        if self._aimConstraintNode != None:
            self._upLocator.attr('worldMatrix[0]') >> self._aimConstraintNode.attr('worldUpMatrix')
    
    upLocator = property( getUpLocator, setUpLocator )
    
    
    
    #----------------------------------------------------------------------
    def getTransformParent(self):
        """get the transform parent
        """
        return self._transformParent
    
    
    
    #----------------------------------------------------------------------
    def setTransformParent(self, transformParent):
        """set the transform parent
        """
        self._transformParent = transformParent
    
    transformParent = property( getTransformParent, setTransformParent )
    
    
    
    #----------------------------------------------------------------------
    def getParentWingPart(self):
        """get the parent wingPart object
        """
        return self._parentWingPart
    
    
    #----------------------------------------------------------------------
    def setParentWingPart(self, parentWingPart):
        """set the parent wingPart
        """
        self._parentWingPart = parentWingPart
    
    parentWingPart = property( getParentWingPart, setParentWingPart )
    
    
    
    #----------------------------------------------------------------------
    def getPrevFeather(self):
        """get the prevFeather
        """
        return self._prevFeather
    
    
    
    #----------------------------------------------------------------------
    def setPrevFeather(self, prevFeather):
        """set the prevFeather
        """
        self._prevFeather = prevFeather
    
    prevFeather = property( getPrevFeather, setPrevFeather )
    
    
    #----------------------------------------------------------------------
    def getNextFeather(self):
        """get the nextFeather
        """
        return self._nextFeather
    
    
    #----------------------------------------------------------------------
    def setNextFeather(self, nextFeather):
        """set the nextFeather
        """
        self._nextFeather = nextFeather
    
    nextFeather = property( getNextFeather, setNextFeather )






########################################################################
class Utility(object):
    """its the utility class that has the basic functions
    """
    
    
    
    #----------------------------------------------------------------------
    def __init__(self):
        pass
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def attachToCurve(obj, curve, param=0.0, parametricLength=True):
        """attaches the given object to the given curve by using a motion path
        
        returns the motion path name
        """
        
        # create the motionPath node
        motionPath = pm.createNode('motionPath')
        
        # connect the curves world space to the motion path
        curveShape = curve.getShape()
        curveShape.attr('worldSpace[0]') >> motionPath.attr('geometryPath')
        
        # connect motionPath.message to obj.specifiedManipLocation
        motionPath.attr('message') >> obj.attr('specifiedManipLocation')
        
        # create three addDoubleLinear nodes
        addDoubleLinear1 = pm.createNode('addDoubleLinear')
        addDoubleLinear2 = pm.createNode('addDoubleLinear')
        addDoubleLinear3 = pm.createNode('addDoubleLinear')
        
        # create a vector product to correct parent transforms
        vectorProd = pm.createNode('vectorProduct')
        
        # connect obj.transMinusRotatePivotX to addDoubleLinear1.input1
        # connect motionPath.xCoordinate to addDoubleLinear1.input2
        obj.attr('transMinusRotatePivotX') >> addDoubleLinear1.attr('input1')
        motionPath.attr('xCoordinate') >> addDoubleLinear1.attr('input2')
        addDoubleLinear1.attr('output') >> vectorProd.attr('input1X')
        
        # connect obj.transMinusRotatePivotY to addDoubleLinear2.input1
        # connect motionPath.yCoordinate to addDoubleLinear2.input2
        obj.attr('transMinusRotatePivotY') >> addDoubleLinear2.attr('input1')
        motionPath.attr('yCoordinate') >> addDoubleLinear2.attr('input2')
        addDoubleLinear2.attr('output') >> vectorProd.attr('input1Y')
        
        # connect obj.transMinusRotatePivotZ to addDoubleLinear3.input1
        # connect motionPath.zCoordinate to addDoubleLinear3.input2
        obj.attr('transMinusRotatePivotZ') >> addDoubleLinear3.attr('input1')
        motionPath.attr('zCoordinate') >> addDoubleLinear3.attr('input2')
        addDoubleLinear3.attr('output') >> vectorProd.attr('input1Z')
        
        # connect the parentInverseMatrix of the obj to vectorProd
        obj.attr('parentInverseMatrix') >> vectorProd.attr('matrix')
        
        # set the operation to point matrix multiplication
        vectorProd.setAttr('operation', 4 )
        
        # connect vectorProd back to the obj
        vectorProd.attr('output') >> obj.attr('t')
        
        # set the motion path parametric length to false ( or the fraction mode
        # to True)
        # and set the param as uValue
        
        motionPath.setAttr('fractionMode', parametricLength)
        motionPath.setAttr('uValue', param)
        
        return motionPath
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def attachToCurve2(obj, curve, param=0.0):
        """attaches the given obj to the given curve by using pointOnCurveInfo
        """
        curveShape = curve.getShape()
        
        pOCI = pm.createNode('pointOnCurveInfo')
        curveShape.attr('worldSpace[0]') >> pOCI.attr('inputCurve')
        
        vectorProd = pm.createNode('vectorProduct')
        obj.attr('parentInverseMatrix[0]') >> vectorProd.attr('matrix')
        
        pOCI.setAttr('turnOnPercentage', 1)
        pOCI.setAttr('parameter', param)
        vectorProd.setAttr('operation',4) # point matrix product
        pOCI.attr('result.position') >> vectorProd.attr('input1')
        vectorProd.attr('output') >> obj.attr('t')
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def circumCurvature( node0, node1, node2):
        """creates a rig that calculates the circum radius of given three
        points
        """
        
        a = pm.createNode('distanceBetween')
        b = pm.createNode('distanceBetween')
        c = pm.createNode('distanceBetween')
        
        # edge0
        node1.attr('worldMatrix[0]') >> a.attr('inMatrix1')
        node0.attr('worldMatrix[0]') >> a.attr('inMatrix2')
        
        # edge1
        node2.attr('worldMatrix[0]') >> b.attr('inMatrix1')
        node1.attr('worldMatrix[0]') >> b.attr('inMatrix2')
        
        #edge2
        node0.attr('worldMatrix[0]') >> c.attr('inMatrix1')
        node2.attr('worldMatrix[0]') >> c.attr('inMatrix2')
        
        mult1 = pm.createNode('multDoubleLinear')
        axbxc = pm.createNode('multDoubleLinear')
        
        a.attr('d') >> mult1.attr('input1')
        b.attr('d') >> mult1.attr('input2')
        mult1.attr('o') >> axbxc.attr('input1')
        c.attr('d') >> axbxc.attr('input2')
        
        # minus_a
        minus_a = pm.createNode('multDoubleLinear')
        a.attr('d') >> minus_a.attr('input1')
        minus_a.setAttr('input2', -1.0)
        
        # minus_b
        minus_b = pm.createNode('multDoubleLinear')
        b.attr('d') >> minus_b.attr('input1')
        minus_b.setAttr('input2', -1.0)
        
        # minus_c
        minus_c = pm.createNode('multDoubleLinear')
        c.attr('d') >> minus_c.attr('input1')
        minus_c.setAttr('input2', -1.0)
        
        # a+b-c
        ab_c = pm.createNode('plusMinusAverage')
        a.attr('d') >> ab_c.attr('input1D[0]')
        b.attr('d') >> ab_c.attr('input1D[1]')
        minus_c.attr('o') >> ab_c.attr('input1D[2]')
        
        # a+c-b
        ac_b = pm.createNode('plusMinusAverage')
        a.attr('d') >> ac_b.attr('input1D[0]')
        c.attr('d') >> ac_b.attr('input1D[1]')
        minus_b.attr('o') >> ac_b.attr('input1D[2]')
        
        # b+c-a
        bc_a = pm.createNode('plusMinusAverage')
        b.attr('d') >> bc_a.attr('input1D[0]')
        c.attr('d') >> bc_a.attr('input1D[1]')
        minus_a.attr('o') >> bc_a.attr('input1D[2]')
        
        # a+b+c
        abc = pm.createNode('plusMinusAverage')
        a.attr('d') >> abc.attr('input1D[0]')
        b.attr('d') >> abc.attr('input1D[1]')
        c.attr('d') >> abc.attr('input1D[2]')
        
        # (a+b+c) * (b+c-a)
        mult2 = pm.createNode('multDoubleLinear')
        abc.attr('output1D') >> mult2.attr('input1')
        bc_a.attr('output1D') >> mult2.attr('input2')
        
        # (a+c-b) * (a+b-c)
        mult3 = pm.createNode('multDoubleLinear')
        ac_b.attr('output1D') >> mult3.attr('input1')
        ab_c.attr('output1D') >> mult3.attr('input2')
        
        # all togather
        mult4 = pm.createNode('multDoubleLinear')
        mult2.attr('o') >> mult4.attr('input1')
        mult3.attr('o') >> mult4.attr('input2')
        
        # sqrt
        sqrt = pm.createNode('multiplyDivide')
        mult4.attr('o') >> sqrt.attr('input1X')
        sqrt.setAttr('input2X', 0.5)
        sqrt.setAttr('operation', 3)
        
        # a*b*c / sqrt is the radius
        # sqrt / a*b*c is the curvature
        circumCurvature = pm.createNode('multiplyDivide')
        sqrt.attr('outputX') >> circumCurvature.attr('input1X')
        axbxc.attr('o') >> circumCurvature.attr('input2X')
        circumCurvature.setAttr('operation', 2 )
        
        return circumCurvature
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def closestPointOnSurface(obj, surface):
        """returns the closest point on the given surface for the given object
        """
        
        sourceTra = pm.xform( obj, q=True, ws=True, t=True)
        
        cPOM = pm.createNode('closestPointOnSurface')
        
        surfaceShape = surface.getShape()
        
        pm.connectAttr( surfaceShape.attr('worldSpace[0]'), cPOM.attr('is') )
        cPOM.setAttr( 'ip', sourceTra)
        
        outTra = cPOM.getAttr('r.p')
        outU = cPOM.getAttr('r.u')
        outV = cPOM.getAttr('r.v')
        
        # delete the cPOM node
        pm.disconnectAttr( surfaceShape.attr('worldSpace[0]'), cPOM.attr('is') )
        pm.delete( cPOM )
        
        return ( outTra, outU, outV )
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def connectToNode(node1, attr1, node2, attr2):
        """connects the animation curve that is connected to node1.attr1 to 
        node2.attr2
        """
        
        animCurveNode = Utility.getAnimationCurve( node1, attr1 )
        
        if animCurveNode != None:
            node2.attr(attr2) >> animCurveNode.attr('input')
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def connectToTime( node, attribute ):
        """connects the animation curve that is connected to the given nodes
        given attribute to time
        """
        
        # get the animation curve
        animCurve = Utility.getAnimationCurve( node, attr )
        timeNode = Utility.getTimeNode()
        
        if animCurve != None:
            
            # store the input before connecting to the time node
            animInput = animCurve.attr('input').inputs[0]
            
            timeNode.attr('outTime') >> animCurve.attr('input')
            
            # if there is any unit conversion node delete them
            if animInput.type() == 'unitToTimeConversion':
                pm.delete( animInput )
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def convertToHair(curve, follicle, hairSystem, parentToFollicle=True):
        """converts the given curve to hair curve by using the follicle and
        hairSystem
        
        returns the hairCurves transform node
        """
        
        # duplicate the given curve under the world
        temp = pm.duplicate(curve)
        dynamicCurve = temp[0]
        
        if dynamicCurve.getParent() is not None:
            pm.parent( dynamicCurve, w=1 )
        
        # set dynamic curves transform to 0
        oyAux.goHome( dynamicCurve )
        
        # get the shapes
        curveShape = curve.getShape()
        dynamicCurveShape = dynamicCurve.getShape()
        follicleShape = follicle.getShape()
        hairSystemShape = hairSystem.getShape()
        
        # connect the dynamic curve to the follicle
        curveShape.attr('worldSpace[0]') >> follicleShape.attr('startPosition')
        follicleShape.attr('outCurve') >> dynamicCurveShape.attr('create')
        
        # connect to the hairSystem
        index = hairSystemShape.attr('inputHair').numElements()
        follicleShape.attr('outHair') >> hairSystemShape.attr('inputHair[' + str(index) +']')
        hairSystemShape.attr('outputHair[' + str(index) + ']') >> follicleShape.attr('currentPosition')
        
        # set the follicle to dynamic
        follicleShape.setAttr('simulationMethod', 2)
        
        # and set it to match the start curve
        follicleShape.setAttr('startDirection', 1 )
        
        # set it to the same degree with the input curve
        follicleShape.setAttr('degree', curveShape.getAttr('degree') )
        
        # parent the base curve under the follicle
        if parentToFollicle:
            pm.parent( curve, follicle )
        
        return dynamicCurve
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createAttributeBlender(obj1, attr1, obj2, attr2, weight=0.0):
        """creates an attribute blender between the given objects and
        attributes
        
        returns the attribute blender node
        """
        
        attrBlender = pm.createNode('blendTwoAttr')
        
        obj1.attr(attr1) >> attrBlender.attr('input[0]')
        obj2.attr(attr2) >> attrBlender.attr('input[1]')
        
        attrBlender.setAttr('ab',weight)
        
        return attrBlender
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createCurve(cvPosList, useAsEditPoints=False ):
        """creates a curve with the cvPos list
        
        if useAsEditPoints is True then the given coordinates are used as edit
        point coordinates
        """
        
        if useAsEditPoints:
            curvePath = pm.curve( d=3, ep=cvPosList )
        else:
            curvePath = pm.curve( d=3, p=cvPosList )
        
        curveNode = pm.PyNode( curvePath )
        return curveNode
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createCurveFromObjs(objList, useAsEditPoints=False):
        """creates a new curve from the given objects list
        """
        posList = [ pm.xform(node, q=1, ws=1, t=1) for node in objList ]
        return Utility.createCurve(posList, useAsEditPoints)
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createCurveWithStartEndPosition( startPos, endPos, cvCount ):
        """creates a third degree curve between the start and end positions
        with givent cvCount
        """
        startPosV = pm.util.VectorN(startPos)
        endPosV = pm.util.VectorN(endPos)
        totalDiff = endPosV - startPosV
        unitDiff = totalDiff / float(cvCount - 1)
        
        cvPosList = []
        cvPosList.append( startPos )
        
        # calculate the inner CV positions
        for i in range(cvCount):
            currentV = startPosV + unitDiff * float(i)
            #assert(isinstance(currentV, pm.util.VectorN))
            cvPosList.append( currentV )
        
        return Utility.createCurve( cvPosList )
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createFollicle(surface, u, v):
        """creates a hair follicle and connects it to the given surface at
        given coordinate
        
        returns the transform node of the created follicle
        """
        
        follicleShape = pm.createNode('follicle')
        
        surface.attr('local') >> follicleShape.attr('inputSurface')
        surface.attr('worldMatrix[0]') >> follicleShape.attr('inputWorldMatrix')
        
        ## get span counts
        #uvSpanCnt = surface.getAttr('spansUV')
        
        # get minMaxRangeU and minMaxRangeV
        minMaxRangeU = surface.getAttr('minMaxRangeU')
        minMaxRangeV = surface.getAttr('minMaxRangeV')
        
        # set uv values
        follicleShape.setAttr ('pu', u/(minMaxRangeU[1]-minMaxRangeU[0]) )
        #follicleShape.setAttr ('pv', v/float(uvSpanCnt[1]))
        follicleShape.setAttr ('pv', v/(minMaxRangeV[1]-minMaxRangeV[0]) )
        
        # set simulation to static
        follicleShape.setAttr ('simulationMethod', 0)
        
        # connect to its transform node
        follicle = follicleShape.getParent()
        
        follicleShape.attr('outTranslate') >> follicle.attr('t')
        follicleShape.attr('outRotate') >> follicle.attr('r')
        
        return follicle
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createHairSystem():
        """creates an empty hair system and returns the transform node of
        the created hairSystem
        """
        
        hairSystemShape = pm.createNode('hairSystem')
        hairSystem = hairSystemShape.getParent()
        
        # connect it to the time1 node
        timeNode = pm.PyNode('time1')
        
        timeNode.attr('outTime') >> hairSystemShape.attr('currentTime')
        
        # just return the transform node
        return hairSystem
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createProjection(node, projectionPlaneLocalNormal=(0,1,0), projectionPlaneObject=None ):
        """creates a projection of the given object in the given local space of
        another object
        
        projectionPlaneNormal defines the plane that the target is going to
        fixed to
        
        if projectionPlaneObject is also defined, the projectionPlaneNormal
        becomes the local axis of the given projectionPlaneObject
        """
        
        # create a locator (projectionLocator)
        # parent it under the projectionPlaneObject
        # zero its transformations out
        # create an axial correction for it
        
        # create another locator (upLocator)
        # parent it under projectionPlaneObject
        # zero its transformations out
        # move it to the projectionPlaneLocalNormal
        
        # aim constraint the axialCorrectionGroups +Y axis to it
        # delete the aimConstraint and upLocator
        # point constraint the node to the projectionLocator in X and Z    
        
        # ******************
        # projection locator
        projectionLocator = pm.spaceLocator()
        
        # rename it:
        projectionLocator.rename( node.name() + '_projectionLocator#' )
        
        pm.parent( projectionLocator, projectionPlaneObject, r=True )
        projectionLocatorACG = oyAux.createAxialCorrectionGroup( projectionLocator )
        
        # ******************
        # up locator
        upLocator = pm.spaceLocator()
        
        # rename it:
        upLocator.rename( node.name() + '_projectionUpLoator#' )
        
        pm.parent( upLocator, projectionPlaneObject, r=True )
        upLocator.setAttr('t', projectionPlaneLocalNormal )
        
        pm.delete( pm.aimConstraint( upLocator,
                          projectionLocatorACG,
                          mo=False,
                          w=1,
                          aimVector=(0,1,0),
                          upVector=(0,0,1),
                          worldUpType='objectrotation',
                          worldUpObject=projectionPlaneObject
                          )
                   , upLocator )
        
        pm.pointConstraint( node, projectionLocator, skip='y' )
        
        return projectionLocator
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createSplineIK(startJoint, endJoint):
        """creates spline IK between given joints
        with advanced twist controls
        """
        # get the joint list
        jointList = Utility.getJointHierarchy( startJoint, endJoint )
        
        # create the curve between joints
        curve = Utility.createCurveFromObjs( jointList, True )
        
        ikHandle = pm.ikHandle( startJoint=startJoint,
                                endEffector=endJoint,
                                curve=curve,
                                solver='ikSplineSolver',
                                twistType='linear',
                                simplifyCurve=False,
                                snapCurve=False,
                                createCurve=False)
        
        # create twist locators and place them to the correct placement
        startTwistLocator = pm.spaceLocator()
        endTwistLocator = pm.spaceLocator()
        
        pm.parent( startTwistLocator, startJoint )
        oyAux.goHome( startTwistLocator )
        startTwistLocator.setAttr('ty', 1)
        pm.parent( startTwistLocator, w=1 )
        
        pm.parent( endTwistLocator, endJoint )
        oyAux.goHome( endTwistLocator )
        endTwistLocator.setAttr('ty', 1)
        pm.parent( endTwistLocator, w=1 )
        
        # set ikHandle advanced twist properties
        ikHandle[0].setAttr('dTwistControlEnable', 1);
        ikHandle[0].setAttr( 'dWorldUpType', 2 )
        
        startTwistLocator.attr('worldMatrix[0]') >> \
                         ikHandle[0].attr('dWorldUpMatrix')
        
        endTwistLocator.attr('worldMatrix[0]') >> \
                       ikHandle[0].attr('dWorldUpMatrixEnd')
        
        
        return ikHandle
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createSurfaceFromCurve(curve, direction, scale=1.0, history=False):
        """creates a new nurbs surface by first offsetting the curve twice,
        and then creating a loft surface between them
        
        direction should be a tupple of 3 floats
        """
        
        # calculate the normal
        normal = Utility.getLocalVector( curve, direction )
        
        # create the offset curves
        temp = pm.offsetCurve( curve, d=(0.5 * scale), nr=normal, ch=history, sd=0 )
        profileCurve1 = temp[0]
        
        temp = pm.offsetCurve( curve, d=-(0.5 * scale), nr=normal, ch=history, sd=0 )
        profileCurve2 = temp[0]
        
        loftSurface = Utility.createSurfaceFromCurves( [profileCurve1, profileCurve2] )
        
        return loftSurface, profileCurve1, profileCurve2
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createSurfaceFromCurves(curves, history=False):
        """creates a surfrace from the given curves
        """
        
        # create the surface
        temp = pm.loft( curves, ar=False, ch=history )
        
        if isinstance(temp, pm.nodetypes.Transform):
            loftSurface = temp
        elif isinstance(temp, list):
            loftSurface = temp[0]
        
        #if not history:
            #pm.delete( profileCurve1, profileCurve2 )
        
        return loftSurface
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def createThreePointArc(node1, node2, node3, history=True):
        """creates a three point arc node from the given nodes
        """
        
        tPA = pm.createNode ('makeThreePointCircularArc')
        
        if history:
            # load decomposeMatrix
            if not pm.pluginInfo ( 'decomposeMatrix', q=True, loaded=True ):
                pm.loadPlugin( 'decomposeMatrix')
            
            dCM1 = pm.createNode('decomposeMatrix')
            dCM2 = pm.createNode('decomposeMatrix')
            dCM3 = pm.createNode('decomposeMatrix')
            
            node1.attr('worldMatrix[0]') >> dCM1.attr('inputMatrix')
            node2.attr('worldMatrix[0]') >> dCM2.attr('inputMatrix')
            node3.attr('worldMatrix[0]') >> dCM3.attr('inputMatrix')
            
            dCM1.attr('outputTranslate') >> tPA.attr('point1')
            dCM2.attr('outputTranslate') >> tPA.attr('point2')
            dCM3.attr('outputTranslate') >> tPA.attr('point3')
        else:
            point1 = pm.xform( node1, q=1, ws=1, t=1 )
            point2 = pm.xform( node2, q=1, ws=1, t=1 )
            point3 = pm.xform( node3, q=1, ws=1, t=1 )
            
            tPA.setAttr('point1', point1)
            tPA.setAttr('point2', point2)
            tPA.setAttr('point3', point3)
        
        return tPA
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def duplicateJointChain(startJoint):
        """duplicates the joint chain by starting from the given joint
        """
        
        assert(isinstance(startJoint, pm.nodetypes.Joint))
        
        # just duplicate the joint
        dupJointList = startJoint.duplicate(rc=True)
        
        currentJoint = dupJointList[0]
        children = currentJoint.getChildren()
        
        while ( len(children) > 0 ):
            
            if len(children) > 1:
                # there should be more than one child delete the others
                pm.delete( children[1:] )
            
            currentJoint = children[0]
            children = currentJoint.getChildren()
        
        return dupJointList[0]
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def duplicateOverBone( joint, obj, count, startFromJoint=True ):
        """duplicates the given object over the bone
        """
        # get the first childs position
        if not Utility.hasChild(joint) or count<=1:
            return []
        
        objList = []
        
        assert(isinstance(joint, pm.nt.Joint))
        childJoint = joint.getChildren()[0]
        
        startPos = pm.xform( joint, q=1, ws=1, t=1 )
        endPos = pm.xform( childJoint, q=1, ws=1, t=1 )
        
        startPosV = pm.util.VectorN( startPos )
        endPosV = pm.util.VectorN( endPos )
        totalDiff = endPosV - startPosV
        
        
        if startFromJoint:
            start = 0
            #stepSize = totalDiff / float(count-1)
        else:
            start = 1
            count += 1
        
        stepSize = totalDiff / float(count-1)
        
        
        for i in range(start,count):
            dup = pm.duplicate( obj, rc=True )
            pm.xform( dup[0], ws=1, t= (startPosV + stepSize * float(i) ) )
            objList.append( dup[0] )
        
        return objList
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def duplicateOverJoint(joint, objToDuplicate, count):
        """duplicates the given objects along the x axis of the given joint
        
        returns the new objects as a list
        """
        
        obj = objToDuplicate
        
        if not Utility.hasChild(joint):
            #count = 1
            return []
        
        jointLength = Utility.getJointLength( joint )
        
        newObjects = []
        dupList = []
        
        interDist = Utility.interleaver( count+1, jointLength )
        
        for i in range(count):
            dupList = obj.duplicate(rc=True)
            
            # place the object to the root of the joint
            dupList = pm.parent( dupList[0], joint, r=True )
            dupObj = pm.PyNode( dupList[0] )
            
            # move it to the place
            # instead of aligning the last object to the end of the joint
            # it leaves a little empty space ot let the other joints feathers
            # fit in
            dupObj.setAttr('tx', float(i+1) * interDist )
            
            newObjects.append( dupObj )
        
        return newObjects
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def duplicateOverJoint2(joint, objToDuplicate, count):
        """duplicates the given objects along the x axis of the given joint,
        but it doesn't start from the begining of the joint, its like adding
        count+2 objects on the joint and returning the only the internal
        objects
        
        returns the new objects as a list
        """
        
        obj = objToDuplicate
        
        if not Utility.hasChild(joint):
            count = 1
        
        jointLength = Utility.getJointLength( joint )
        
        newObjects = []
        dupList = []
        
        interDist = Utility.interleaver( count+2, jointLength )
        
        for i in range(count):
            dupList = obj.duplicate(rc=True)
            
            # place the object to the root of the joint
            dupList = pm.parent( dupList[0], joint, r=True )
            dupObj = pm.PyNode( dupList[0] )
            
            # move it to the place
            # instead of aligning the last object to the end of the joint
            # it leaves a little empty space ot let the other joints feathers
            # fit in
            dupObj.setAttr('tx', ( float(i) + 1.0 ) * interDist )
            
            newObjects.append( dupObj )
        
        return newObjects
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def extendJoint(joint, placement=1, direction=1, extendAmount=1.0):
        """creates a new joint from the given joint at the given direction
        
        placement defines where to place the joint
        placement=0 places it to the start of the joint
        placement=1 places it to the start of the child joint
        
        direction 1 is the default and extends towards the child
        direction -1 extends towards the negative child (means the negative
        direction when you use 1 )
        """
        
        # get the first child
        child = Utility.getChildOfType( joint, pm.nodetypes.Joint )
        
        # duplicate the original joint
        extendedJoint = pm.duplicate( joint )[0]
        
        assert(isinstance(extendedJoint, pm.nodetypes.Joint) )
        
        # delete all the children of the new joint
        pm.delete( extendedJoint.getChildren() )
        
        # parent it to the original joint
        pm.parent( extendedJoint, joint )
        
        # duplicate it self and parent the new joint to the extendedJoint
        extendedChild = pm.duplicate( extendedJoint )[0]
        
        pm.parent( extendedChild, extendedJoint )
        
        extendedChild.setAttr('tx', extendAmount )
        
        # move it to the original joints childs position
        if placement == 1:
            tra = pm.xform( child, q=1, ws=1, t=1 )
            pm.xform( extendedJoint, ws=1, t=tra )
            
        if direction == -1:
            extendedJoint.setAttr('ry', 180)
            pm.makeIdentity( extendedJoint, apply=1, r=1 )
        
        #pm.select( extendedJoint )
        
        return extendedJoint
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getAnimationCurve(node1, attr1):
        """returns the animation curve that is connected to node1.attr1
        """
        #assert(isinstance(node1, pm.nodetypes.Transform))
        
        returnNode = None
        
        inputs = node1.attr(attr1).inputs()
        
        
        if len(inputs) > 0:
            if isinstance( inputs[0], pm.nodetypes.AnimCurve ):
                returnNode = inputs[0]
        
        return returnNode
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getAttributeSign(node, attr):
        """returns the given nodes given attributes sign
        U for doubleLinear and double
        A for doubleAngle
        """
        
        nodeAttrType = node.attr( attr ).type()
        nodeAttrTypeSign = ''
        
        if nodeAttrType == 'doubleLinear' or nodeAttrType == 'double':
            nodeAttrTypeSign = 'U'
        elif nodeAttrType == 'doubleAngle':
            nodeAttrTypeSign = 'A'
        
        return nodeAttrTypeSign
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getChildOfType(node, childType ):
        """returns the first child that is an instance of childType or None
        if the child is not an instance of that type
        
        Examples:
        1- to get a child Mesh
        getChildOfType( node, pm.nodetypes.Transform, pm.nodetypes.Mesh )
        """
        
        shapeType = None
        
        if childType == pm.nodetypes.Camera or \
           childType == pm.nodetypes.CurveShape or \
           childType == pm.nodetypes.Locator or \
           childType == pm.nodetypes.Lattice or \
           childType == pm.nodetypes.Mesh or \
           childType == pm.nodetypes.NurbsCurve or \
           childType == pm.nodetypes.NurbsSurface or \
           childType == pm.nodetypes.Subdiv or \
           childType == pm.nodetypes.Particle:
            shapeType = childType
            childType = pm.nodetypes.Transform
        
        #assert(isinstance(joint, pm.nodetypes.Joint))
        
        children = node.getChildren()
        childOfType = None
        
        if len(children) > 0:
            # check if the child is in childType
            for child in children:
                if isinstance(child, childType):
                    if shapeType is not None:
                        # get the shape
                        shape = child.getShape()
                        if isinstance( shape, shapeType ):
                            childOfType = child
                            break
                    else:
                        childOfType = child
                        break
        
        return childOfType
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getJointHierarchy(startJoint, endJoint):
        """returns a group of joints from the start to the end joints
        """
        
        assert( isinstance(startJoint, pm.nodetypes.Joint))
        
        jointList = []
       
        jointList.append( startJoint )
        
        childJoint = Utility.getChildOfType( startJoint, pm.nodetypes.Joint )
        currentJoint = childJoint
        
        while ( currentJoint != endJoint and currentJoint is not None ):
            
            jointList.append( childJoint )
            childJoint = Utility.getChildOfType( currentJoint, pm.nodetypes.Joint )
            currentJoint = childJoint
        
        jointList.append( endJoint )
        return jointList
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getJointLength(joint):
        """returns the joint length
        """
        
        # get the first child and get the childs x position and return
        # it as the joint length
        
        childs = joint.listRelatives(c=True)
        
        if len(childs) < 1:
            return 0.0
        
        length = childs[0].getAttr('tx')
        
        return length
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getJointList(startJoint, jointCount):
        """returns a list that contains the joints that are nth children of the
        start joint
        """
        
        jointList = []
        jointList.append( startJoint )
        
        currentJoint = startJoint
        
        for i in range(jointCount-1):
            currentJoint = currentJoint.childAtIndex(0)
            jointList.append( currentJoint )
        
        return jointList
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getLocalVector(obj, vector):
        """returns the vector transformed to the given objects space
        """
        
        vectorN = pm.util.VectorN( vector )
        matrixN = pm.util.MatrixN( obj.getAttr('worldMatrix[0]') )
        
        return vectorN * matrixN
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def getTimeNode():
        """returns the default time node
        """
        return pm.ls('time1')[0]
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def groupBlendAttributes(objList, attrName):
        """blends the attributes of the given objects by weighting them
        between the first and the last in the list
        """
        
        if len(objList) < 1:
            return
        
        startObj = objList[0]
        endObj = objList[-1]
        
        objCount = len(objList)
        
        for i in range(1, objCount - 1 ):
            attrBlender = Utility.createAttributeBlender( startObj, attrName, endObj, attrName, weight = (float(i)/float(objCount-1)) )
            
            attrBlender.attr('output') >> objList[i].attr(attrName)
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def groupConstraint(objList, type='point', maintainOfset=False):
        """constraints the given objects to the objects in the each end of the
        list
        
        available constraint types are
        point
        orient
        parent
        scale
        """
        
        startObj = objList[0]
        endObj = objList[-1]
        
        if type == 'point':
            consCommand = pm.pointConstraint
        elif type == 'orient':
            consCommand = pm.orientConstraint
        elif type == 'parent':
            consCommand = pm.parentConstraint
        elif type == 'scale':
            consCommand = pm.scaleConstraint
        else:
            consCommand = pm.pointConstraint
        
        objCount = len(objList)
        
        for i in range(1, objCount - 1):
            consCommand( startObj, objList[i], w=(objCount-1-i), mo=maintainOfset )
            consCommand( endObj, objList[i], w=i, mo=maintainOfset )
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def hasChild(joint):
        """returns True if the joint has childs
        """
        return len(joint.listRelatives(c=True)) > 0
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def interleaver(count, distance):
        """calculates the neccesary distance between elements to fit them to
        the given distance
        """
        
        if count > 1:
            return (float(distance) / float(count-1))
        else:
            return 0.0
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def prefixNodeName(node, prefix):
        """adds the given prefix to the given node name
        """
        
        #assert(isinstance(node, pm.nodetypes.DependNode))
        node.rename( prefix + "_" + node.name() )
    
    
    
    #----------------------------------------------------------------------
    @staticmethod
    def smartBend( node, direction=[1,0,0] ):
        """creates a bend rig which is controlled easier than a normal bend
        deformer
        """
        
        # create the rigs root transform node
        xform = pm.group( em=True )
        
        # ********************
        # The Locators
        # ********************
        
        # Second Point Locator
        secondPointLoc = pm.spaceLocator()
        pm.parent( secondPointLoc, xform)
        oyAux.goHome( secondPointLoc )
        #secondPointLoc.setAttr('ty',0.01)
        secondPointLoc.setAttr('v', 0)
        
        # The Local representation of the controller
        localThirdpoint = pm.spaceLocator()
        pm.parent( localThirdpoint, xform )
        localThirdpoint.setAttr('v', 0)
        
        # The Controller
        controller = pm.spaceLocator()
        pm.parent( controller, xform )
        controller.setAttr('ty',1)
        
        # point constraint the localThirdPoint to controller
        pm.pointConstraint( controller, localThirdpoint, w=1 )
        
        # set the secondPoint to 0.01 of the thirdPoint
        divider = pm.createNode( 'multDoubleLinear')
        divider.setAttr('input2', 0.01)
        localThirdpoint.attr('ty') >> divider.attr('input1')
        divider.attr('o') >> secondPointLoc.attr('ty')
        
        
        # ******************
        # Bend
        # ********************
        bend = pm.nonLinear( node, type='bend', defaultScale=True )
        # parent it under xform
        pm.parent( bend[1], xform )
        oyAux.goHome( bend[1] )
        
        
        # create a projection of the controller to the xform
        projectionLoc = Utility.createProjection( controller, (0,1,0), xform )
        
        # aim the bend to the projection locator
        pm.aimConstraint( projectionLoc,
                          bend[1],
                          mo=False,
                          w=1,
                          aimVector=(1,0,0),
                          upVector=(0,1,0),
                          worldUpType='objectrotation',
                          worldUpObject=xform )
        
        # parent xform to the object
        pm.parent( xform, node )
        oyAux.goHome( xform )
        
        # rotate the xform to the direction
        #localDirection = Utility.getLocalVector( node, direction )
        
        rotation = pm.modeling.angleBetween( euler=True, v1=[0,1,0], v2=direction )
        xform.setAttr('r', rotation)
        
        threePointArc = Utility.createThreePointArc( xform, secondPointLoc, controller )
        circumCurvatureNode = Utility.circumCurvature( xform, secondPointLoc, controller )
        
        # to the bend
        circumCurvatureNode.attr('outputX') >> bend[0].attr('curvature')
        
        # create a curveInfo node
        curveInfo = pm.createNode('curveInfo')
        threePointArc.attr('outputCurve') >> curveInfo.attr('inputCurve')
        
        curveInfo.attr('arcLength') >> bend[0].attr('highBound')
        
        # set the bounds
        bend[0].setAttr( 'lowBound', 0 )
        
        return controller, xform






#########################################################################
#class UI(object):
    #"""the userInterface for the wing feather setup
    #"""
    
    
    
    ##----------------------------------------------------------------------
    #def __init__(self):
        
        
        ## create interface
        ## show the interface
        #pass
    
    
    
    ##----------------------------------------------------------------------
    #def create(self):
        #"""creates the UI
        #"""
        
        #self.window = pm.Window(t='oyWingFeatherSetup v' +  __version__ )
        
        #self.jointAxis_radioButtonCollection = pm.RadioCollection()
        
        #pass
    
    
    
    ##----------------------------------------------------------------------
    #def show(self):
        #"""shows the UI
        #"""
        #pass
    
    
    
    ##----------------------------------------------------------------------
    #def delete(self):
        #"""deletes the UI
        #"""






########################################################################
class ProfileJoint(object):
    """a profile joint class to help manage data
    """
    
    
    
    #----------------------------------------------------------------------
    def __init__(self):
        self._ACGroup = None
        self._rootJoint = None
        self._tipJoint = None
        self._controllers = []
        self._constraint = None
        self._constraintGroup = None
        self._length = 1
        
        self._isCreated = False
        
        self.create()
    
    
    
    #----------------------------------------------------------------------
    def createACGroup(self):
        """creates the axial correction group
        """
        if self._constraintGroup == None:
            self.createConstraintGroup()
        
        self._ACGroup = oyAux.createAxialCorrectionGroup( self._constraintGroup )
    
    
    
    #----------------------------------------------------------------------
    def createConstraintGroup(self):
        """creates the constraint group
        """
        if self._constraintGroup == None:
            self._constraintGroup = oyAux.createAxialCorrectionGroup( self._rootJoint )
    
    
    
    #----------------------------------------------------------------------
    @property
    def ACGroup(self):
        """returns the axial correction group
        """
        return self._ACGroup
    
    
    
    #----------------------------------------------------------------------
    @property
    def constraintGroup(self):
        """returns the constraintGroup
        """
        return self._constraintGroup
    
    
    
    ##----------------------------------------------------------------------
    #def rootJoint():
        #def fget(self):
            #return self._rootJoint
        
        #def fset(self, joint):
            #self._rootJoint = joint
        
        #return locals()
    
    #rootJoint = property( **rootJoint() )
    
    #----------------------------------------------------------------------
    @property
    def rootJoint(self):
        """returns the rootJoint
        """
        return self._rootJoint
    
    
    
    ##----------------------------------------------------------------------
    #def tipJoint():
        #def fget(self):
            #return self._tipJoint
        
        #def fset(self, joint):
            #self._tipJoint = joint
        
        #return locals()
    
    #tipJoint = property( **tipJoint() )
    
    
    
    #----------------------------------------------------------------------
    @property
    def tipJoint(self):
        """returns the tip joint
        """
        return self._tipJoint
    
    
    
    #----------------------------------------------------------------------
    def controllers():
        
        def fget(self):
            return self._controllers
        
        def fset(self, controllers ):
            self._controllers = controllers
        
        return locals()
    
    controllers = property( **controllers() )
    
    
    
    #----------------------------------------------------------------------
    def connectToControllers(self):
        """connects the joint to the controllers by creating parent constraint
        """
        
        # check if there is a constraint group
        if self._constraintGroup == None and \
           self._ACGroup == None:
            # create the constraintGroup
            self.createACGroup()
        
        pm.select( None )
        self._constraint = pm.parentConstraint(
            tuple(self._controllers),
            #self._rootJoint,
            self._constraintGroup,
            mo=False,
            w=1 )
        
        self._constraint.setAttr('interpType', 2)
    
    
    
    #----------------------------------------------------------------------
    def create(self):
        """creates the joints
        """
        if self._isCreated:
            return
        
        pm.select(None)
        self._rootJoint = pm.joint( p=(0,0,0) )
        self._tipJoint = pm.joint( p=(self._length,0,0) )
        
        # also create ACGroup and constraintGroup
        self.createConstraintGroup()
        self.createACGroup()
        
        self._isCreated = True
    
    
    
    #----------------------------------------------------------------------
    def length():
        
        def fget(self):
            return self._length
        
        def fset(self, length):
            self._length = length
            # also move the tipJoint
            self._tipJoint.setAttr('tx', self._length )
        
        return locals()
    
    length = property( **length() )






########################################################################
class WingJoint(object):
    """a wing joint is the smallest component that makes a wing
    """
    
    
    
    #----------------------------------------------------------------------
    def __init__(self, joint=None):
        self._joint = joint
        self._rootFin = None
        self._rootFinTip = None
        self._tipFin = None
        self._tipFinTip = None
        self._hasFins = False
        self._finLength = 1
    
    
    
    #----------------------------------------------------------------------
    def __str__(self):
        """return the str representation of the joint
        """
        return self._joint.name()
    
    
    
    #----------------------------------------------------------------------
    def joint():
        def fget(self):
            return self._joint
        
        def fset(self, joint):
            self._joint = joint
        
        return locals()
    
    joint = property( **joint() )
    
    
    
    #----------------------------------------------------------------------
    @property
    def fins(self):
        """returns the fins
        """
        return self._rootFin, self._tipFin
    
    
    
    #----------------------------------------------------------------------
    @property
    def rootFin(self):
        return self._rootFin
    
    
    
    #----------------------------------------------------------------------
    @property
    def rootFinTip(self):
        return self._rootFinTip
    
    
    
    #----------------------------------------------------------------------
    @property
    def tipFin(self):
        return self._tipFin
    
    
    
    #----------------------------------------------------------------------
    @property
    def tipFinTip(self):
        return self._tipFinTip
    
    
    
    #----------------------------------------------------------------------
    def createFins(self):
        """creates the root and tip fins
        """
        
        if self._hasFins:
            return
        
        self._rootFin = Utility.extendJoint( self._joint, 0, -1, self._finLength )
        self._tipFin = Utility.extendJoint( self._joint, 1, 1, self._finLength )
        
        self._rootFin.setAttr('ry', 90)
        self._tipFin.setAttr('ry', -90)
        
        # also create axial correction groups for the fins
        oyAux.createAxialCorrectionGroup( self._rootFin )
        oyAux.createAxialCorrectionGroup( self._tipFin )
        
        self._rootFinTip = self._rootFin.getChildren()[0]
        self._tipFinTip = self._tipFin.getChildren()[0]
        
        self._hasFins = True
    
    
    
    #----------------------------------------------------------------------
    def finLength():
        def fget(self):
            return self._length
        
        def fset(self, length):
            assert(isinstance(self, WingJoint))
            
            self._finLength = length
            
            # also set the tx of fin tips
            if self._hasFins:
                self._rootFinTip.setAttr( 'tx', length )
                self._tipFinTip.setAttr( 'tx', length )
        
        return locals()
    
    finLength = property( **finLength() )